| #!/usr/bin/env python3 |
| # Copyright 2014 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Runs a test repeatedly to measure its flakiness. The return code is non-zero |
| if the failure rate is higher than the specified threshold, but is not 100%.""" |
| |
| import argparse |
| import multiprocessing.dummy |
| import subprocess |
| import sys |
| import time |
| |
| def load_options(): |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument('--retries', default=1000, type=int, |
| help='Number of test retries to measure flakiness.') |
| parser.add_argument('--threshold', default=0.05, type=float, |
| help='Minimum flakiness level at which test is ' |
| 'considered flaky.') |
| parser.add_argument('--jobs', '-j', type=int, default=1, |
| help='Number of parallel jobs to run tests.') |
| parser.add_argument('command', nargs='+', help='Command to run test.') |
| return parser.parse_args() |
| |
| def run_test(job): |
| print('Starting retry attempt %d out of %d' % (job['index'] + 1, |
| job['retries'])) |
| return subprocess.check_call(job['cmd'], stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT) |
| |
| def main(): |
| options = load_options() |
| num_passed = num_failed = 0 |
| running = [] |
| |
| pool = multiprocessing.dummy.Pool(processes=options.jobs) |
| args = [{'index': index, 'retries': options.retries, 'cmd': options.command} |
| for index in range(options.retries)] |
| results = pool.map(run_test, args) |
| num_passed = len([retcode for retcode in results if retcode == 0]) |
| num_failed = len(results) - num_passed |
| |
| if num_passed == 0: |
| flakiness = 0 |
| else: |
| flakiness = num_failed / float(len(results)) |
| |
| print('Flakiness is %.2f' % flakiness) |
| if flakiness > options.threshold: |
| return 1 |
| else: |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |