Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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_unittest 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
36 changes: 36 additions & 0 deletions test/retryable_unittest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import common
import os
import unittest

EMTEST_RETRY_COUNT = int(os.getenv('EMTEST_RETRY_COUNT', '0'))


class RetryableTestCase(unittest.TestCase):
'''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.'''

def run(self, result=None):
retries_left = EMTEST_RETRY_COUNT

num_fails = len(result.failures)
num_errors = len(result.errors)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these part of the non-parallel test runner too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these are part of the default unittest.py unittest.TestCase implementation. Had to add them to the parallel test runner to match the shape of the upstream Python implementation. Verified that this works in single-threaded and parallel test runner.


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(), EMTEST_RETRY_COUNT - retries_left, EMTEST_RETRY_COUNT, str(err))