OLD | NEW |
1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. | 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 | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 | 5 |
6 """Shards a given test suite and runs the shards in parallel. | 6 """Shards a given test suite and runs the shards in parallel. |
7 | 7 |
8 ShardingSupervisor is called to process the command line options and creates | 8 ShardingSupervisor is called to process the command line options and creates |
9 the specified number of worker threads. These threads then run each shard of | 9 the specified number of worker threads. These threads then run each shard of |
10 the test in a separate process and report on the results. When all the shards | 10 the test in a separate process and report on the results. When all the shards |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
74 | 74 |
75 | 75 |
76 class ShardRunner(threading.Thread): | 76 class ShardRunner(threading.Thread): |
77 """Worker thread that manages a single shard at a time. | 77 """Worker thread that manages a single shard at a time. |
78 | 78 |
79 Attributes: | 79 Attributes: |
80 supervisor: The ShardingSupervisor that this worker reports to. | 80 supervisor: The ShardingSupervisor that this worker reports to. |
81 counter: Called to get the next shard index to run. | 81 counter: Called to get the next shard index to run. |
82 """ | 82 """ |
83 | 83 |
84 def __init__(self, supervisor, counter, test_fail, test_timeout): | 84 def __init__(self, supervisor, counter, test_start, test_ok, test_fail): |
85 """Inits ShardRunner with a supervisor and counter.""" | 85 """Inits ShardRunner with a supervisor and counter.""" |
86 threading.Thread.__init__(self) | 86 threading.Thread.__init__(self) |
87 self.supervisor = supervisor | 87 self.supervisor = supervisor |
88 self.counter = counter | 88 self.counter = counter |
| 89 self.test_start = test_start |
| 90 self.test_ok = test_ok |
89 self.test_fail = test_fail | 91 self.test_fail = test_fail |
90 self.test_timeout = test_timeout | 92 self.current_test = "" |
91 | 93 |
92 def SearchForFailure(self, regex, prefix, line, description): | 94 def ReportFailure(self, description, prefix, test_name): |
93 results = regex.search(line) | 95 log_line = "%s: %s%s\n" % (description, prefix, test_name) |
| 96 self.supervisor.LogLineFailure(log_line) |
| 97 |
| 98 def ProcessLine(self, prefix, line): |
| 99 results = self.test_start.search(line) |
94 if results: | 100 if results: |
95 log_line = "%s: %s%s\n" % (description, prefix, results.group(1)) | 101 if self.current_test: |
96 self.supervisor.LogLineFailure(log_line) | 102 self.ReportFailure("INCOMPLETE", prefix, self.current_test) |
97 return True | 103 self.current_test = results.group(1) |
98 return False | 104 return |
| 105 |
| 106 results = self.test_ok.search(line) |
| 107 if results: |
| 108 self.current_test = "" |
| 109 return |
| 110 |
| 111 results = self.test_fail.search(line) |
| 112 if results: |
| 113 self.ReportFailure("FAILED", prefix, results.group(1)) |
| 114 self.current_test = "" |
99 | 115 |
100 def run(self): | 116 def run(self): |
101 """Runs shards and outputs the results. | 117 """Runs shards and outputs the results. |
102 | 118 |
103 Gets the next shard index from the supervisor, runs it in a subprocess, | 119 Gets the next shard index from the supervisor, runs it in a subprocess, |
104 and collects the output. Each line is prefixed with 'N>', where N is the | 120 and collects the output. Each line is prefixed with 'N>', where N is the |
105 current shard index. | 121 current shard index. |
106 """ | 122 """ |
107 while True: | 123 while True: |
108 try: | 124 try: |
109 index = self.counter.get_nowait() | 125 index = self.counter.get_nowait() |
110 except Queue.Empty: | 126 except Queue.Empty: |
111 break | 127 break |
112 prefix = "%i>" % index | 128 prefix = "%i>" % index |
113 chars = StringIO() | 129 chars = StringIO() |
114 shard_running = True | 130 shard_running = True |
115 shard = RunShard( | 131 shard = RunShard( |
116 self.supervisor.test, self.supervisor.num_shards, index, | 132 self.supervisor.test, self.supervisor.num_shards, index, |
117 self.supervisor.gtest_args, subprocess.PIPE, subprocess.STDOUT) | 133 self.supervisor.gtest_args, subprocess.PIPE, subprocess.STDOUT) |
118 while shard_running: | 134 while shard_running: |
119 char = shard.stdout.read(1) | 135 char = shard.stdout.read(1) |
120 if not char and shard.poll() is not None: | 136 if not char and shard.poll() is not None: |
121 shard_running = False | 137 shard_running = False |
122 chars.write(char) | 138 chars.write(char) |
123 if char == "\n" or not shard_running: | 139 if char == "\n" or not shard_running: |
124 line = chars.getvalue() | 140 line = chars.getvalue() |
125 if not line and not shard_running: | 141 if not line and not shard_running: |
126 break | 142 break |
127 if not self.SearchForFailure( | 143 self.ProcessLine(prefix, line) |
128 self.test_fail, prefix, line, "FAILED"): | |
129 self.SearchForFailure(self.test_timeout, prefix, line, "TIMEOUT") | |
130 line = prefix + line | 144 line = prefix + line |
131 self.supervisor.LogOutputLine(index, line) | 145 self.supervisor.LogOutputLine(index, line) |
132 chars.close() | 146 chars.close() |
133 chars = StringIO() | 147 chars = StringIO() |
134 self.supervisor.ShardIndexCompleted(index) | 148 self.supervisor.ShardIndexCompleted(index) |
135 if shard.returncode != 0: | 149 if shard.returncode != 0: |
136 self.supervisor.LogShardFailure(index) | 150 self.supervisor.LogShardFailure(index) |
137 | 151 |
138 | 152 |
139 class ShardingSupervisor(object): | 153 class ShardingSupervisor(object): |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 """ | 192 """ |
179 | 193 |
180 # Regular expressions for parsing GTest logs. Test names look like | 194 # Regular expressions for parsing GTest logs. Test names look like |
181 # SomeTestCase.SomeTest | 195 # SomeTestCase.SomeTest |
182 # SomeName/SomeTestCase.SomeTest/1 | 196 # SomeName/SomeTestCase.SomeTest/1 |
183 # This regex also matches SomeName.SomeTest/1 and | 197 # This regex also matches SomeName.SomeTest/1 and |
184 # SomeName/SomeTestCase.SomeTest, which should be harmless. | 198 # SomeName/SomeTestCase.SomeTest, which should be harmless. |
185 test_name_regex = r"((\w+/)?\w+\.\w+(/\d+)?)" | 199 test_name_regex = r"((\w+/)?\w+\.\w+(/\d+)?)" |
186 | 200 |
187 # Regex for filtering out ANSI escape codes when using color. | 201 # Regex for filtering out ANSI escape codes when using color. |
188 ansi_code_regex = r"(?:\x1b\[.*?[a-zA-Z])?" | 202 ansi_regex = r"(?:\x1b\[.*?[a-zA-Z])?" |
189 | 203 |
| 204 test_start = re.compile( |
| 205 ansi_regex + r"\[\s+RUN\s+\] " + ansi_regex + test_name_regex) |
| 206 test_ok = re.compile( |
| 207 ansi_regex + r"\[\s+OK\s+\] " + ansi_regex + test_name_regex) |
190 test_fail = re.compile( | 208 test_fail = re.compile( |
191 ansi_code_regex + "\[\s+FAILED\s+\] " + ansi_code_regex + | 209 ansi_regex + r"\[\s+FAILED\s+\] " + ansi_regex + test_name_regex) |
192 test_name_regex) | |
193 test_timeout = re.compile( | |
194 "Test timeout \([0-9]+ ms\) exceeded for " + test_name_regex) | |
195 | 210 |
196 workers = [] | 211 workers = [] |
197 counter = Queue.Queue() | 212 counter = Queue.Queue() |
198 for i in range(self.num_shards): | 213 for i in range(self.num_shards): |
199 counter.put(i) | 214 counter.put(i) |
200 | 215 |
201 # Disable stdout buffering to read shard output one character at a time. | |
202 # This allows for shard crashes that do not end with a "\n". | |
203 sys.stdout = os.fdopen(sys.stdout.fileno(), "w", 0) | |
204 | |
205 for i in range(self.num_runs): | 216 for i in range(self.num_runs): |
206 worker = ShardRunner(self, counter, test_fail, test_timeout) | 217 worker = ShardRunner( |
| 218 self, counter, test_start, test_ok, test_fail) |
207 worker.start() | 219 worker.start() |
208 workers.append(worker) | 220 workers.append(worker) |
209 if self.reorder: | 221 if self.reorder: |
210 self.WaitForShards() | 222 self.WaitForShards() |
211 else: | 223 else: |
212 for worker in workers: | 224 for worker in workers: |
213 worker.join() | 225 worker.join() |
214 | 226 |
215 # Re-enable stdout buffering. | 227 return self.PrintSummary(self.failure_log) |
216 sys.stdout = sys.__stdout__ | |
217 | |
218 return self.PrintSummary() | |
219 | 228 |
220 def LogLineFailure(self, line): | 229 def LogLineFailure(self, line): |
221 """Saves a line in the failure log to be printed at the end. | 230 """Saves a line in the failure log to be printed at the end. |
222 | 231 |
223 Args: | 232 Args: |
224 line: The line to save in the failure_log. | 233 line: The line to save in the failure_log. |
225 """ | 234 """ |
226 if line not in self.failure_log: | 235 if line not in self.failure_log: |
227 self.failure_log.append(line) | 236 self.failure_log.append(line) |
228 | 237 |
(...skipping 15 matching lines...) Expand all Loading... |
244 | 253 |
245 def LogOutputLine(self, index, line): | 254 def LogOutputLine(self, index, line): |
246 if self.reorder: | 255 if self.reorder: |
247 self.shard_output[index].put(line) | 256 self.shard_output[index].put(line) |
248 else: | 257 else: |
249 sys.stdout.write(line) | 258 sys.stdout.write(line) |
250 | 259 |
251 def ShardIndexCompleted(self, index): | 260 def ShardIndexCompleted(self, index): |
252 self.shard_output[index].put(self.SHARD_COMPLETED) | 261 self.shard_output[index].put(self.SHARD_COMPLETED) |
253 | 262 |
254 def PrintSummary(self): | 263 def PrintSummary(self, failed_tests): |
255 """Prints a summary of the test results. | 264 """Prints a summary of the test results. |
256 | 265 |
257 If any shards had failing tests, the list is sorted and printed. Then all | 266 If any shards had failing tests, the list is sorted and printed. Then all |
258 the lines that indicate a test failure are reproduced. | 267 the lines that indicate a test failure are reproduced. |
259 | 268 |
260 Returns: | 269 Returns: |
261 The number of shards that had failing tests. | 270 The number of shards that had failing tests. |
262 """ | 271 """ |
263 sys.stderr.write("\n") | 272 sys.stderr.write("\n") |
264 num_failed = len(self.failed_shards) | 273 num_failed = len(self.failed_shards) |
265 if num_failed > 0: | 274 if num_failed > 0: |
266 self.failed_shards.sort() | 275 self.failed_shards.sort() |
267 if self.color: | 276 self.WriteText(sys.stderr, |
268 sys.stderr.write("\x1b[1;5;31m") | 277 "FAILED SHARDS: %s\n" % str(self.failed_shards), |
269 sys.stderr.write("SHARDS THAT FAILED: %s\n" % str(self.failed_shards)) | 278 "\x1b[1;5;31m") |
270 else: | 279 else: |
271 if self.color: | 280 self.WriteText(sys.stderr, "ALL SHARDS PASSED!\n", "\x1b[1;5;32m") |
272 sys.stderr.write("\x1b[1;5;32m") | 281 if failed_tests: |
273 sys.stderr.write("ALL SHARDS PASSED!\n") | 282 self.WriteText(sys.stderr, "FAILED TESTS:\n", "\x1b[1;5;31m") |
274 if self.failure_log: | 283 for line in failed_tests: |
275 if self.color: | |
276 sys.stderr.write("\x1b[1;5;31m") | |
277 sys.stderr.write("TESTS THAT DID NOT PASS:\n") | |
278 if self.color: | |
279 sys.stderr.write("\x1b[m") | |
280 for line in self.failure_log: | |
281 sys.stderr.write(line) | 284 sys.stderr.write(line) |
282 if self.color: | 285 if self.color: |
283 sys.stderr.write("\x1b[m") | 286 sys.stderr.write("\x1b[m") |
284 return num_failed | 287 return num_failed |
285 | 288 |
| 289 def WriteText(self, pipe, text, ansi): |
| 290 if self.color: |
| 291 pipe.write(ansi) |
| 292 pipe.write(text) |
| 293 if self.color: |
| 294 pipe.write("\x1b[m") |
| 295 |
286 | 296 |
287 def main(): | 297 def main(): |
288 parser = optparse.OptionParser(usage=SS_USAGE) | 298 parser = optparse.OptionParser(usage=SS_USAGE) |
289 parser.add_option( | 299 parser.add_option( |
290 "-n", "--shards_per_core", type="int", default=SS_DEFAULT_SHARDS_PER_CORE, | 300 "-n", "--shards_per_core", type="int", default=SS_DEFAULT_SHARDS_PER_CORE, |
291 help="number of shards to generate per CPU") | 301 help="number of shards to generate per CPU") |
292 parser.add_option( | 302 parser.add_option( |
293 "-r", "--runs_per_core", type="int", default=SS_DEFAULT_RUNS_PER_CORE, | 303 "-r", "--runs_per_core", type="int", default=SS_DEFAULT_RUNS_PER_CORE, |
294 help="number of shards to run in parallel per CPU") | 304 help="number of shards to run in parallel per CPU") |
295 parser.add_option( | 305 parser.add_option( |
296 "-c", "--color", action="store_true", default=sys.stdout.isatty(), | 306 "-c", "--color", action="store_true", |
| 307 default=sys.platform != "win32" and sys.stdout.isatty(), |
297 help="force color output, also used by gtest if --gtest_color is not" | 308 help="force color output, also used by gtest if --gtest_color is not" |
298 " specified") | 309 " specified") |
299 parser.add_option( | 310 parser.add_option( |
300 "--no-color", action="store_false", dest="color", | 311 "--no-color", action="store_false", dest="color", |
301 help="disable color output") | 312 help="disable color output") |
302 parser.add_option( | 313 parser.add_option( |
303 "-s", "--runshard", type="int", help="single shard index to run") | 314 "-s", "--runshard", type="int", help="single shard index to run") |
304 parser.add_option( | 315 parser.add_option( |
305 "--reorder", action="store_true", | 316 "--reorder", action="store_true", |
306 help="ensure that all output from an earlier shard is printed before" | 317 help="ensure that all output from an earlier shard is printed before" |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
343 | 354 |
344 # shard and run the whole test | 355 # shard and run the whole test |
345 ss = ShardingSupervisor(args[0], num_shards, num_runs, options.color, | 356 ss = ShardingSupervisor(args[0], num_shards, num_runs, options.color, |
346 options.reorder, gtest_args) | 357 options.reorder, gtest_args) |
347 return ss.ShardTest() | 358 return ss.ShardTest() |
348 | 359 |
349 | 360 |
350 if __name__ == "__main__": | 361 if __name__ == "__main__": |
351 sys.exit(main()) | 362 sys.exit(main()) |
352 | 363 |
OLD | NEW |