@@ -96,10 +96,11 @@ def get_module(name, path): |
96 | 96 |
|
97 | 97 | class ProgressIndicator(object): |
98 | 98 |
|
99 | | - def __init__(self, cases, flaky_tests_mode): |
| 99 | + def __init__(self, cases, flaky_tests_mode, measure_flakiness): |
100 | 100 | self.cases = cases |
101 | 101 | self.serial_id = 0 |
102 | 102 | self.flaky_tests_mode = flaky_tests_mode |
| 103 | + self.measure_flakiness = measure_flakiness |
103 | 104 | self.parallel_queue = Queue(len(cases)) |
104 | 105 | self.sequential_queue = Queue(len(cases)) |
105 | 106 | for case in cases: |
@@ -211,10 +212,22 @@ def RunSingle(self, parallel, thread_id): |
211 | 212 | if output.UnexpectedOutput(): |
212 | 213 | if FLAKY in output.test.outcomes and self.flaky_tests_mode == DONTCARE: |
213 | 214 | self.flaky_failed.append(output) |
| 215 | + elif FLAKY in output.test.outcomes and self.flaky_tests_mode == KEEP_RETRYING: |
| 216 | + for _ in range(99): |
| 217 | + if not case.Run().UnexpectedOutput(): |
| 218 | + self.flaky_failed.append(output) |
| 219 | + break |
| 220 | + else: |
| 221 | + # If after 100 tries, the test is not passing, it's not flaky. |
| 222 | + self.failed.append(output) |
214 | 223 | else: |
215 | 224 | self.failed.append(output) |
216 | 225 | if output.HasCrashed(): |
217 | 226 | self.crashed += 1 |
| 227 | + if self.measure_flakiness: |
| 228 | + outputs = [case.Run() for _ in range(self.measure_flakiness)] |
| 229 | + # +1s are there because the test already failed once at this point. |
| 230 | + print(f" failed {len([i for i in outputs if i.UnexpectedOutput()]) + 1} out of {self.measure_flakiness + 1}") |
218 | 231 | else: |
219 | 232 | self.succeeded += 1 |
220 | 233 | self.remaining -= 1 |
@@ -436,8 +449,8 @@ def Done(self): |
436 | 449 |
|
437 | 450 | class CompactProgressIndicator(ProgressIndicator): |
438 | 451 |
|
439 | | - def __init__(self, cases, flaky_tests_mode, templates): |
440 | | - super(CompactProgressIndicator, self).__init__(cases, flaky_tests_mode) |
| 452 | + def __init__(self, cases, flaky_tests_mode, measure_flakiness, templates): |
| 453 | + super(CompactProgressIndicator, self).__init__(cases, flaky_tests_mode, measure_flakiness) |
441 | 454 | self.templates = templates |
442 | 455 | self.last_status_length = 0 |
443 | 456 | self.start_time = time.time() |
@@ -492,29 +505,29 @@ def PrintProgress(self, name): |
492 | 505 |
|
493 | 506 | class ColorProgressIndicator(CompactProgressIndicator): |
494 | 507 |
|
495 | | - def __init__(self, cases, flaky_tests_mode): |
| 508 | + def __init__(self, cases, flaky_tests_mode, measure_flakiness): |
496 | 509 | templates = { |
497 | 510 | 'status_line': "[%(mins)02i:%(secs)02i|\033[34m%%%(remaining) 4d\033[0m|\033[32m+%(passed) 4d\033[0m|\033[31m-%(failed) 4d\033[0m]: %(test)s", |
498 | 511 | 'stdout': "\033[1m%s\033[0m", |
499 | 512 | 'stderr': "\033[31m%s\033[0m", |
500 | 513 | } |
501 | | - super(ColorProgressIndicator, self).__init__(cases, flaky_tests_mode, templates) |
| 514 | + super(ColorProgressIndicator, self).__init__(cases, flaky_tests_mode, measure_flakiness, templates) |
502 | 515 |
|
503 | 516 | def ClearLine(self, last_line_length): |
504 | 517 | print("\033[1K\r", end='') |
505 | 518 |
|
506 | 519 |
|
507 | 520 | class MonochromeProgressIndicator(CompactProgressIndicator): |
508 | 521 |
|
509 | | - def __init__(self, cases, flaky_tests_mode): |
| 522 | + def __init__(self, cases, flaky_tests_mode, measure_flakiness): |
510 | 523 | templates = { |
511 | 524 | 'status_line': "[%(mins)02i:%(secs)02i|%%%(remaining) 4d|+%(passed) 4d|-%(failed) 4d]: %(test)s", |
512 | 525 | 'stdout': '%s', |
513 | 526 | 'stderr': '%s', |
514 | 527 | 'clear': lambda last_line_length: ("\r" + (" " * last_line_length) + "\r"), |
515 | 528 | 'max_length': 78 |
516 | 529 | } |
517 | | - super(MonochromeProgressIndicator, self).__init__(cases, flaky_tests_mode, templates) |
| 530 | + super(MonochromeProgressIndicator, self).__init__(cases, flaky_tests_mode, measure_flakiness, templates) |
518 | 531 |
|
519 | 532 | def ClearLine(self, last_line_length): |
520 | 533 | print(("\r" + (" " * last_line_length) + "\r"), end='') |
@@ -948,8 +961,8 @@ def GetTimeout(self, mode, section=''): |
948 | 961 | timeout = timeout * 6 |
949 | 962 | return timeout |
950 | 963 |
|
951 | | -def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode): |
952 | | - progress = PROGRESS_INDICATORS[progress](cases_to_run, flaky_tests_mode) |
| 964 | +def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode, measure_flakiness): |
| 965 | + progress = PROGRESS_INDICATORS[progress](cases_to_run, flaky_tests_mode, measure_flakiness) |
953 | 966 | return progress.Run(tasks) |
954 | 967 |
|
955 | 968 | # ------------------------------------------- |
@@ -967,6 +980,7 @@ def RunTestCases(cases_to_run, progress, tasks, flaky_tests_mode): |
967 | 980 | SLOW = 'slow' |
968 | 981 | FLAKY = 'flaky' |
969 | 982 | DONTCARE = 'dontcare' |
| 983 | +KEEP_RETRYING = 'keep_retrying' |
970 | 984 |
|
971 | 985 | class Expression(object): |
972 | 986 | pass |
@@ -1355,8 +1369,11 @@ def BuildOptions(): |
1355 | 1369 | result.add_option("--cat", help="Print the source of the tests", |
1356 | 1370 | default=False, action="store_true") |
1357 | 1371 | result.add_option("--flaky-tests", |
1358 | | - help="Regard tests marked as flaky (run|skip|dontcare)", |
| 1372 | + help="Regard tests marked as flaky (run|skip|dontcare|keep_retrying)", |
1359 | 1373 | default="run") |
| 1374 | + result.add_option("--measure-flakiness", |
| 1375 | + help="When a test fails, re-run it x number of times", |
| 1376 | + default=0, type="int") |
1360 | 1377 | result.add_option("--skip-tests", |
1361 | 1378 | help="Tests that should not be executed (comma-separated)", |
1362 | 1379 | default="") |
@@ -1433,7 +1450,7 @@ def ProcessOptions(options): |
1433 | 1450 | # -j and ignoring -J, which is the opposite of what we used to do before -J |
1434 | 1451 | # became a legacy no-op. |
1435 | 1452 | print('Warning: Legacy -J option is ignored. Using the -j option.') |
1436 | | - if options.flaky_tests not in [RUN, SKIP, DONTCARE]: |
| 1453 | + if options.flaky_tests not in [RUN, SKIP, DONTCARE, KEEP_RETRYING]: |
1437 | 1454 | print("Unknown flaky-tests mode %s" % options.flaky_tests) |
1438 | 1455 | return False |
1439 | 1456 | return True |
@@ -1733,7 +1750,7 @@ def should_keep(case): |
1733 | 1750 | else: |
1734 | 1751 | try: |
1735 | 1752 | start = time.time() |
1736 | | - if RunTestCases(cases_to_run, options.progress, options.j, options.flaky_tests): |
| 1753 | + if RunTestCases(cases_to_run, options.progress, options.j, options.flaky_tests, options.measure_flakiness): |
1737 | 1754 | result = 0 |
1738 | 1755 | else: |
1739 | 1756 | result = 1 |
|