OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2011 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 all the test cases in a given test in parallel with itself, to get at | |
Paweł Hajdan Jr.
2011/08/19 18:35:50
Would it make sense to combine this in a single fi
clee
2011/08/19 21:06:35
Ideally yes, but the command line options to the t
| |
7 those that hold on to shared resources. The idea is that if a test uses a | |
8 unary resource, then running many instances of this test will purge out some | |
9 of them as failures or timeouts. | |
10 """ | |
11 | |
12 | |
13 import optparse | |
14 import os | |
15 import re | |
16 import subprocess | |
17 import time | |
18 | |
19 | |
20 PF_USAGE = 'python %prog [options] path/to/test' | |
21 PF_DEFAULT_OUTPUT_SUFFIX = '_purges' | |
22 PF_DEFAULT_NUM_PROCS = 20 | |
23 PF_DEFAULT_NUM_REPEATS = 10 | |
24 PF_DEFAULT_TIMEOUT = 600 | |
25 | |
26 | |
27 def PurgeFlakies(test_path, output_path, num_procs, num_repeats, timeout): | |
28 test_name_regex = r'((\w+/)?\w+\.\w+(/\d+)?)' | |
29 test_start = re.compile('\[\s+RUN\s+\] ' + test_name_regex) | |
30 test_list = [] | |
31 | |
32 # run the test to discover all the test cases | |
33 proc = subprocess.Popen([test_path], stdout=subprocess.PIPE) | |
34 while True: | |
35 line = proc.stdout.readline() | |
36 if not line: | |
37 if proc.poll() is not None: | |
38 break | |
39 continue | |
40 print line.rstrip() | |
41 results = test_start.search(line) | |
42 if results: | |
43 test_list.append(results.group(1)) | |
44 | |
45 failures = [] | |
46 index = 0 | |
47 total = len(test_list) | |
48 | |
49 # run each test case in parallel with itself | |
50 for test_name in test_list: | |
51 num_fails = 0 | |
52 num_terminated = 0 | |
53 procs = [] | |
54 args = [test_path, '--gtest_filter=' + test_name, | |
55 '--gtest_repeat=%i' % num_repeats] | |
56 while len(procs) < num_procs: | |
57 procs.append(subprocess.Popen(args)) | |
58 seconds = 0 | |
59 while procs: | |
60 for proc in procs: | |
61 if proc.poll() is not None: | |
62 if proc.returncode != 0: | |
63 ++num_fails | |
64 procs.remove(proc) | |
65 # timeout exceeded, kill the remaining processes and make a note | |
66 if seconds > timeout: | |
67 num_fails += len(procs) | |
68 num_terminated = len(procs) | |
69 while procs: | |
70 procs.pop().terminate() | |
71 time.sleep(1.0) | |
72 seconds += 1 | |
73 if num_fails: | |
74 line = '%s: %i failed' % (test_name, num_fails) | |
75 if num_terminated: | |
76 line += ' (%i terminated)' % num_terminated | |
77 failures.append(line) | |
78 print '%s (%i / %i): %i failed' % (test_name, index, total, num_fails) | |
79 index += 1 | |
80 time.sleep(1.0) | |
81 | |
82 # print the results and write the data file | |
83 print failures | |
84 data_file = open(output_path, 'w') | |
85 for line in failures: | |
86 data_file.write(line + '\n') | |
87 data_file.close() | |
88 | |
89 | |
90 def main(): | |
91 parser = optparse.OptionParser(usage=PF_USAGE) | |
92 parser.add_option( | |
93 '--output-path', | |
94 help='path to output file (default is to append "%s" to the end of the' | |
95 ' test name in the current directory)' % PF_DEFAULT_OUTPUT_SUFFIX) | |
96 parser.add_option( | |
97 '--procs', type='int', default=PF_DEFAULT_NUM_PROCS, | |
98 help='number of parallel test processes to start up' | |
99 ' (default = %s)' % PF_DEFAULT_NUM_PROCS) | |
100 parser.add_option( | |
101 '--repeats', type='int', default=PF_DEFAULT_NUM_REPEATS, | |
102 help='number of times to repeat each test in each process' | |
103 ' (default = %s)' % PF_DEFAULT_NUM_REPEATS) | |
104 parser.add_option( | |
105 '--timeout', type='int', default=PF_DEFAULT_TIMEOUT, | |
106 help='test processes are killed if their runtimes exceed the timeout,' | |
107 ' useful for some tests (default = %s)' % PF_DEFAULT_TIMEOUT) | |
108 (options, args) = parser.parse_args() | |
109 | |
110 if not args: | |
111 parser.error('You must specify a path to test!') | |
112 if not os.path.exists(args[0]): | |
113 parser.error('%s does not exist!' % args[0]) | |
114 | |
115 if not options.output_path: | |
116 options.output_path = os.path.basename(args[0]) + PF_DEFAULT_OUTPUT_SUFFIX | |
117 | |
118 PurgeFlakies(args[0], options.output_path, options.procs, options.repeats, | |
119 options.timeout) | |
120 | |
121 | |
122 if __name__ == '__main__': | |
123 main() | |
OLD | NEW |