Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(113)

Side by Side Diff: gtest-parallel

Issue 2678373002: Roll gtest-parallel cac6d27..a5a21a7 (Closed)
Patch Set: Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « README.webrtc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python2 1 #!/usr/bin/env python2
2 # Copyright 2013 Google Inc. All rights reserved. 2 # Copyright 2013 Google Inc. All rights reserved.
3 # 3 #
4 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License. 5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at 6 # You may obtain a copy of the License at
7 # 7 #
8 # http://www.apache.org/licenses/LICENSE-2.0 8 # http://www.apache.org/licenses/LICENSE-2.0
9 # 9 #
10 # Unless required by applicable law or agreed to in writing, software 10 # Unless required by applicable law or agreed to in writing, software
(...skipping 29 matching lines...) Expand all
40 # wait(p) will call p.terminate() and raise ProcessWasInterrupted. 40 # wait(p) will call p.terminate() and raise ProcessWasInterrupted.
41 class SigintHandler(object): 41 class SigintHandler(object):
42 class ProcessWasInterrupted(Exception): pass 42 class ProcessWasInterrupted(Exception): pass
43 sigint_returncodes = {-signal.SIGINT, # Unix 43 sigint_returncodes = {-signal.SIGINT, # Unix
44 -1073741510, # Windows 44 -1073741510, # Windows
45 } 45 }
46 def __init__(self): 46 def __init__(self):
47 self.__lock = threading.Lock() 47 self.__lock = threading.Lock()
48 self.__processes = set() 48 self.__processes = set()
49 self.__got_sigint = False 49 self.__got_sigint = False
50 signal.signal(signal.SIGINT, self.__sigint_handler) 50 signal.signal(signal.SIGINT, lambda signal_num, frame: self.interrupt())
51 def __on_sigint(self): 51 def __on_sigint(self):
52 self.__got_sigint = True 52 self.__got_sigint = True
53 while self.__processes: 53 while self.__processes:
54 try: 54 try:
55 self.__processes.pop().terminate() 55 self.__processes.pop().terminate()
56 except OSError: 56 except OSError:
57 pass 57 pass
58 def __sigint_handler(self, signal_num, frame): 58 def interrupt(self):
59 with self.__lock: 59 with self.__lock:
60 self.__on_sigint() 60 self.__on_sigint()
61 def got_sigint(self): 61 def got_sigint(self):
62 with self.__lock: 62 with self.__lock:
63 return self.__got_sigint 63 return self.__got_sigint
64 def wait(self, p): 64 def wait(self, p):
65 with self.__lock: 65 with self.__lock:
66 if self.__got_sigint: 66 if self.__got_sigint:
67 p.terminate() 67 p.terminate()
68 self.__processes.add(p) 68 self.__processes.add(p)
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 if sys.stdout.isatty(): 120 if sys.stdout.isatty():
121 # stdout needs to be unbuffered since the output is interactive. 121 # stdout needs to be unbuffered since the output is interactive.
122 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) 122 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
123 123
124 out = Outputter(sys.stdout) 124 out = Outputter(sys.stdout)
125 total_tests = 0 125 total_tests = 0
126 finished_tests = 0 126 finished_tests = 0
127 127
128 tests = {} 128 tests = {}
129 outputs = {} 129 outputs = {}
130 started = []
131 finished = []
130 failures = [] 132 failures = []
131 133
134 def print_tests(self, message, test_ids):
135 if test_ids:
136 self.out.permanent_line("%s (%s/%s):" %
137 (message, len(test_ids), self.total_tests))
138 test_ids = sorted(test_ids, key=lambda test_id: self.tests[test_id])
139 for test_id in test_ids:
140 self.out.permanent_line(" %s: %s" % self.tests[test_id])
141
132 def print_test_status(self, last_finished_test, time_ms): 142 def print_test_status(self, last_finished_test, time_ms):
133 self.out.transient_line("[%d/%d] %s (%d ms)" 143 self.out.transient_line("[%d/%d] %s (%d ms)"
134 % (self.finished_tests, self.total_tests, 144 % (self.finished_tests, self.total_tests,
135 last_finished_test, time_ms)) 145 last_finished_test, time_ms))
136 146
137 def handle_meta(self, job_id, args): 147 def handle_meta(self, job_id, args):
138 (command, arg) = args.split(' ', 1) 148 (command, arg) = args.split(' ', 1)
139 if command == "TEST": 149 if command == "TEST":
140 (binary, test) = arg.split(' ', 1) 150 (binary, test) = arg.split(' ', 1)
141 self.tests[job_id] = (binary, test.strip()) 151 self.tests[job_id] = (binary, test.strip())
152 elif command == "START":
153 self.started.append(job_id)
142 elif command == "EXIT": 154 elif command == "EXIT":
143 (exit_code, time_ms) = [int(x) for x in arg.split(' ', 1)] 155 (exit_code, time_ms) = [int(x) for x in arg.split(' ', 1)]
144 self.finished_tests += 1 156 self.finished_tests += 1
157 self.finished.append(job_id)
145 (binary, test) = self.tests[job_id] 158 (binary, test) = self.tests[job_id]
146 self.print_test_status(test, time_ms) 159 self.print_test_status(test, time_ms)
147 if exit_code != 0: 160 if exit_code != 0:
148 self.failures.append(self.tests[job_id]) 161 self.failures.append(job_id)
149 with open(self.outputs[job_id]) as f: 162 with open(self.outputs[job_id]) as f:
150 for line in f.readlines(): 163 for line in f.readlines():
151 self.out.permanent_line(line.rstrip()) 164 self.out.permanent_line(line.rstrip())
152 self.out.permanent_line( 165 self.out.permanent_line(
153 "[%d/%d] %s returned/aborted with exit code %d (%d ms)" 166 "[%d/%d] %s returned/aborted with exit code %d (%d ms)"
154 % (self.finished_tests, self.total_tests, test, exit_code, time_ms)) 167 % (self.finished_tests, self.total_tests, test, exit_code, time_ms))
155 elif command == "TESTCNT": 168 elif command == "TESTCNT":
156 self.total_tests = int(arg.split(' ', 1)[1]) 169 self.total_tests = int(arg.split(' ', 1)[1])
157 self.out.transient_line("[0/%d] Running tests..." % self.total_tests) 170 self.out.transient_line("[0/%d] Running tests..." % self.total_tests)
158 171
159 def logfile(self, job_id, name): 172 def logfile(self, job_id, name):
160 self.outputs[job_id] = name 173 self.outputs[job_id] = name
161 174
162 def log(self, line): 175 def log(self, line):
163 stdout_lock.acquire() 176 stdout_lock.acquire()
164 (prefix, output) = line.split(' ', 1) 177 (prefix, output) = line.split(' ', 1)
165 178
166 assert prefix[-1] == ':' 179 assert prefix[-1] == ':'
167 self.handle_meta(int(prefix[:-1]), output) 180 self.handle_meta(int(prefix[:-1]), output)
168 stdout_lock.release() 181 stdout_lock.release()
169 182
170 def end(self): 183 def end(self):
171 if self.failures: 184 self.print_tests("FAILED TESTS", self.failures)
172 self.out.permanent_line("FAILED TESTS (%d/%d):" 185 interruptions = set(self.started) - set(self.finished)
173 % (len(self.failures), self.total_tests)) 186 self.print_tests("INTERRUPTED TESTS", interruptions)
174 for (binary, test) in self.failures:
175 self.out.permanent_line(" " + binary + ": " + test)
176 self.out.flush_transient_output() 187 self.out.flush_transient_output()
177 188
178 class RawFormat: 189 class RawFormat:
179 def log(self, line): 190 def log(self, line):
180 stdout_lock.acquire() 191 stdout_lock.acquire()
181 sys.stdout.write(line + "\n") 192 sys.stdout.write(line + "\n")
182 sys.stdout.flush() 193 sys.stdout.flush()
183 stdout_lock.release() 194 stdout_lock.release()
184 def logfile(self, job_id, name): 195 def logfile(self, job_id, name):
185 with open(name) as f: 196 with open(name) as f:
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
217 def dump_to_file_and_close(self): 228 def dump_to_file_and_close(self):
218 json.dump(self.test_results, self.json_dump_file) 229 json.dump(self.test_results, self.json_dump_file)
219 self.json_dump_file.close() 230 self.json_dump_file.close()
220 231
221 class IgnoreTestResults(object): 232 class IgnoreTestResults(object):
222 def log(self, test, result): 233 def log(self, test, result):
223 pass 234 pass
224 def dump_to_file_and_close(self): 235 def dump_to_file_and_close(self):
225 pass 236 pass
226 237
238 class DummyTimer(object):
239 def start(self):
240 pass
241 def cancel(self):
242 pass
243
227 # Record of test runtimes. Has built-in locking. 244 # Record of test runtimes. Has built-in locking.
228 class TestTimes(object): 245 class TestTimes(object):
229 def __init__(self, save_file): 246 def __init__(self, save_file):
230 "Create new object seeded with saved test times from the given file." 247 "Create new object seeded with saved test times from the given file."
231 self.__times = {} # (test binary, test name) -> runtime in ms 248 self.__times = {} # (test binary, test name) -> runtime in ms
232 249
233 # Protects calls to record_test_time(); other calls are not 250 # Protects calls to record_test_time(); other calls are not
234 # expected to be made concurrently. 251 # expected to be made concurrently.
235 self.__lock = threading.Lock() 252 self.__lock = threading.Lock()
236 253
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
307 parser.add_option('--shard_count', type='int', default=1, 324 parser.add_option('--shard_count', type='int', default=1,
308 help='total number of shards (for sharding test execution ' 325 help='total number of shards (for sharding test execution '
309 'between multiple machines)') 326 'between multiple machines)')
310 parser.add_option('--shard_index', type='int', default=0, 327 parser.add_option('--shard_index', type='int', default=0,
311 help='zero-indexed number identifying this shard (for ' 328 help='zero-indexed number identifying this shard (for '
312 'sharding test execution between multiple machines)') 329 'sharding test execution between multiple machines)')
313 parser.add_option('--dump_json_test_results', type='string', default=None, 330 parser.add_option('--dump_json_test_results', type='string', default=None,
314 help='Saves the results of the tests as a JSON machine-' 331 help='Saves the results of the tests as a JSON machine-'
315 'readable file. The format of the file is specified at ' 332 'readable file. The format of the file is specified at '
316 'https://www.chromium.org/developers/the-json-test-result s-format') 333 'https://www.chromium.org/developers/the-json-test-result s-format')
334 parser.add_option('--timeout', type='int', default=None,
335 help='Interrupt all remaining processes after the given '
336 'time (in seconds).')
317 337
318 (options, binaries) = parser.parse_args() 338 (options, binaries) = parser.parse_args()
319 339
320 if binaries == []: 340 if binaries == []:
321 parser.print_usage() 341 parser.print_usage()
322 sys.exit(1) 342 sys.exit(1)
323 343
324 logger = RawFormat() 344 logger = RawFormat()
325 if options.format == 'raw': 345 if options.format == 'raw':
326 pass 346 pass
327 elif options.format == 'filter': 347 elif options.format == 'filter':
328 logger = FilterFormat() 348 logger = FilterFormat()
329 else: 349 else:
330 parser.error("Unknown output format: " + options.format) 350 parser.error("Unknown output format: " + options.format)
331 351
332 if options.shard_count < 1: 352 if options.shard_count < 1:
333 parser.error("Invalid number of shards: %d. Must be at least 1." % 353 parser.error("Invalid number of shards: %d. Must be at least 1." %
334 options.shard_count) 354 options.shard_count)
335 if not (0 <= options.shard_index < options.shard_count): 355 if not (0 <= options.shard_index < options.shard_count):
336 parser.error("Invalid shard index: %d. Must be between 0 and %d " 356 parser.error("Invalid shard index: %d. Must be between 0 and %d "
337 "(less than the number of shards)." % 357 "(less than the number of shards)." %
338 (options.shard_index, options.shard_count - 1)) 358 (options.shard_index, options.shard_count - 1))
339 359
360 timeout = (DummyTimer() if options.timeout is None
361 else threading.Timer(options.timeout, sigint_handler.interrupt))
362
340 test_results = (IgnoreTestResults() if options.dump_json_test_results is None 363 test_results = (IgnoreTestResults() if options.dump_json_test_results is None
341 else CollectTestResults(options.dump_json_test_results)) 364 else CollectTestResults(options.dump_json_test_results))
342 365
343 # Find tests. 366 # Find tests.
344 save_file = os.path.join(os.path.expanduser("~"), ".gtest-parallel-times") 367 save_file = os.path.join(os.path.expanduser("~"), ".gtest-parallel-times")
345 times = TestTimes(save_file) 368 times = TestTimes(save_file)
346 tests = [] 369 tests = []
347 for test_binary in binaries: 370 for test_binary in binaries:
348 command = [test_binary] 371 command = [test_binary]
349 if options.gtest_also_run_disabled_tests: 372 if options.gtest_also_run_disabled_tests:
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
411 # Remove files from old test runs. 434 # Remove files from old test runs.
412 for logfile in os.listdir(options.output_dir): 435 for logfile in os.listdir(options.output_dir):
413 os.remove(os.path.join(options.output_dir, logfile)) 436 os.remove(os.path.join(options.output_dir, logfile))
414 437
415 # Run the specified job. Return the elapsed time in milliseconds if 438 # Run the specified job. Return the elapsed time in milliseconds if
416 # the job succeeds, or None if the job fails. (This ensures that 439 # the job succeeds, or None if the job fails. (This ensures that
417 # failing tests will run first the next time.) 440 # failing tests will run first the next time.)
418 def run_job((command, job_id, test, test_index)): 441 def run_job((command, job_id, test, test_index)):
419 begin = time.time() 442 begin = time.time()
420 443
444 logger.log("%s: START " % job_id)
421 test_name = re.sub('[^A-Za-z0-9]', '_', test) + '-' + str(test_index) + '.log' 445 test_name = re.sub('[^A-Za-z0-9]', '_', test) + '-' + str(test_index) + '.log'
422 with open(os.path.join(options.output_dir, test_name), 'w') as log: 446 with open(os.path.join(options.output_dir, test_name), 'w') as log:
423 sub = subprocess.Popen(command + ['--gtest_filter=' + test] + 447 sub = subprocess.Popen(command + ['--gtest_filter=' + test] +
424 ['--gtest_color=' + options.gtest_color], 448 ['--gtest_color=' + options.gtest_color],
425 stdout=log, stderr=log) 449 stdout=log, stderr=log)
426 try: 450 try:
427 code = sigint_handler.wait(sub) 451 code = sigint_handler.wait(sub)
428 except sigint_handler.ProcessWasInterrupted: 452 except sigint_handler.ProcessWasInterrupted:
429 thread.exit() 453 thread.exit()
430 runtime_ms = int(1000 * (time.time() - begin)) 454 runtime_ms = int(1000 * (time.time() - begin))
(...skipping 25 matching lines...) Expand all
456 if job is None: 480 if job is None:
457 return 481 return
458 times.record_test_time(test_binary, test, run_job(job)) 482 times.record_test_time(test_binary, test, run_job(job))
459 483
460 def start_daemon(func): 484 def start_daemon(func):
461 t = threading.Thread(target=func) 485 t = threading.Thread(target=func)
462 t.daemon = True 486 t.daemon = True
463 t.start() 487 t.start()
464 return t 488 return t
465 489
466 workers = [start_daemon(worker) for i in range(options.workers)] 490 try:
491 timeout.start()
492 workers = [start_daemon(worker) for i in range(options.workers)]
493 [t.join() for t in workers]
494 finally:
495 timeout.cancel()
467 496
468 [t.join() for t in workers]
469 logger.end() 497 logger.end()
470 times.write_to_file(save_file) 498 times.write_to_file(save_file)
471 if options.print_test_times: 499 if options.print_test_times:
472 ts = sorted((times.get_test_time(test_binary, test), test_binary, test) 500 ts = sorted((times.get_test_time(test_binary, test), test_binary, test)
473 for (_, test_binary, test, _) in tests 501 for (_, test_binary, test, _) in tests
474 if times.get_test_time(test_binary, test) is not None) 502 if times.get_test_time(test_binary, test) is not None)
475 for (time_ms, test_binary, test) in ts: 503 for (time_ms, test_binary, test) in ts:
476 print "%8s %s" % ("%dms" % time_ms, test) 504 print "%8s %s" % ("%dms" % time_ms, test)
477 505
478 test_results.dump_to_file_and_close() 506 test_results.dump_to_file_and_close()
479 sys.exit(-signal.SIGINT if sigint_handler.got_sigint() else exit_code) 507 sys.exit(-signal.SIGINT if sigint_handler.got_sigint() else exit_code)
OLDNEW
« no previous file with comments | « README.webrtc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698