Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Dict, Tuple
from urllib.parse import unquote, unquote_plus, urlparse, parse_qs
from http.server import ThreadingHTTPServer, SimpleHTTPRequestHandler
from retryable_unit_test import RetryableTestCase
import contextlib
import difflib
import hashlib
Expand Down Expand Up @@ -286,8 +287,8 @@ def is_slow_test(func):
return decorated


def record_flaky_test(test_name, attempt_count, exception_msg):
logging.info(f'Retrying flaky test "{test_name}" (attempt {attempt_count}/{EMTEST_RETRY_FLAKY} failed):\n{exception_msg}')
def record_flaky_test(test_name, attempt_count, max_attempts, exception_msg):
logging.info(f'Retrying flaky test "{test_name}" (attempt {attempt_count}/{max_attempts} failed):\n{exception_msg}')
open(flaky_tests_log_filename, 'a').write(f'{test_name}\n')


Expand All @@ -313,7 +314,7 @@ def modified(self, *args, **kwargs):
return func(self, *args, **kwargs)
except (AssertionError, subprocess.TimeoutExpired) as exc:
preserved_exc = exc
record_flaky_test(self.id(), i, exc)
record_flaky_test(self.id(), i, EMTEST_RETRY_FLAKY, exc)

raise AssertionError('Flaky test has failed too many times') from preserved_exc

Expand Down Expand Up @@ -1032,7 +1033,7 @@ def __new__(mcs, name, bases, attrs):
return type.__new__(mcs, name, bases, new_attrs)


class RunnerCore(unittest.TestCase, metaclass=RunnerMeta):
class RunnerCore(RetryableTestCase, metaclass=RunnerMeta):
# default temporary directory settings. set_temp_dir may be called later to
# override these
temp_dir = shared.TEMP_DIR
Expand Down Expand Up @@ -2774,7 +2775,7 @@ def run_browser(self, html_file, expected=None, message=None, timeout=None, extr
self.assertContained(expected, output)
except self.failureException as e:
if extra_tries > 0:
record_flaky_test(self.id(), EMTEST_RETRY_FLAKY - extra_tries, e)
record_flaky_test(self.id(), EMTEST_RETRY_FLAKY - extra_tries, EMTEST_RETRY_FLAKY, e)
if not self.capture_stdio:
print('[enabling stdio/stderr reporting]')
self.capture_stdio = True
Expand Down
4 changes: 4 additions & 0 deletions test/parallel_testsuite.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ def __init__(self, lock, progress_counter, num_tests):
self.lock = lock
self.progress_counter = progress_counter
self.num_tests = num_tests
self.failures = []
self.errors = []

@property
def test(self):
Expand Down Expand Up @@ -336,12 +338,14 @@ def addFailure(self, test, err):
errlog(f'{self.compute_progress()}{with_color(RED, msg)}')
self.buffered_result = BufferedTestFailure(test, err)
self.test_result = 'failed'
self.failures += [test]

def addError(self, test, err):
msg = f'{test} ... ERROR'
errlog(f'{self.compute_progress()}{with_color(RED, msg)}')
self.buffered_result = BufferedTestError(test, err)
self.test_result = 'errored'
self.errors += [test]


class BufferedTestBase:
Expand Down
34 changes: 34 additions & 0 deletions test/retryable_unit_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import common
import os
import unittest

# This class patches in to the Python unittest TestCase object to incorporate
# support for an environment variable EMTEST_RETRY_COUNT=x, which enables a
# failed test to be automatically re-run to test if the failure might have been
# due to an instability.
class RetryableTestCase(unittest.TestCase):
def run(self, result=None):
self.origTestMethodName = self._testMethodName
test_retry_count = int(os.getenv('EMTEST_RETRY_COUNT', '0'))
retries_left = test_retry_count

num_fails = len(result.failures)
num_errors = len(result.errors)

while retries_left >= 0:
super().run(result)

# The test passed if it didn't accumulate an error.
if len(result.failures) == num_fails and len(result.errors) == num_errors:
return

retries_left -= 1
if retries_left >= 0:
if len(result.failures) != num_fails:
err = result.failures.pop(-1)
elif len(result.errors) != num_errors:
err = result.errors.pop(-1)
else:
raise Exception('Internal error in RetryableTestCase: did not detect an error')

common.record_flaky_test(self.id(), test_retry_count - retries_left, test_retry_count, str(err))
Loading