1#!/usr/bin/env python
2# Copyright 2014 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Runs a test repeatedly to measure its flakiness. The return code is non-zero
7if the failure rate is higher than the specified threshold, but is not 100%."""
8
9import argparse
10import multiprocessing.dummy
11import subprocess
12import sys
13import time
14
15def load_options():
16  parser = argparse.ArgumentParser(description=__doc__)
17  parser.add_argument('--retries', default=1000, type=int,
18                      help='Number of test retries to measure flakiness.')
19  parser.add_argument('--threshold', default=0.05, type=float,
20                      help='Minimum flakiness level at which test is '
21                           'considered flaky.')
22  parser.add_argument('--jobs', '-j', type=int, default=1,
23                      help='Number of parallel jobs to run tests.')
24  parser.add_argument('command', nargs='+', help='Command to run test.')
25  return parser.parse_args()
26
27def run_test(job):
28  print 'Starting retry attempt %d out of %d' % (job['index'] + 1,
29                                                 job['retries'])
30  return subprocess.check_call(job['cmd'], stdout=subprocess.PIPE,
31                               stderr=subprocess.STDOUT)
32
33def main():
34  options = load_options()
35  num_passed = num_failed = 0
36  running = []
37
38  pool = multiprocessing.dummy.Pool(processes=options.jobs)
39  args = [{'index': index, 'retries': options.retries, 'cmd': options.command}
40          for index in range(options.retries)]
41  results = pool.map(run_test, args)
42  num_passed = len([retcode for retcode in results if retcode == 0])
43  num_failed = len(results) - num_passed
44
45  if num_passed == 0:
46    flakiness = 0
47  else:
48    flakiness = num_failed / float(len(results))
49
50  print 'Flakiness is %.2f' % flakiness
51  if flakiness > options.threshold:
52    return 1
53  else:
54    return 0
55
56
57if __name__ == '__main__':
58  sys.exit(main())
59