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

Side by Side Diff: tools/sharding_supervisor/sharding_supervisor.py

Issue 10082014: Modify the sharding_supervisor to understand gtest's xml output. (Closed) Base URL: svn://svn-mirror.golo.chromium.org/chrome/trunk/src/
Patch Set: Created 8 years, 8 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 | « no previous file | 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 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
11 have been completed, the supervisor reprints any lines indicating a test 11 have been completed, the supervisor reprints any lines indicating a test
12 failure for convenience. If only one shard is to be run, a single subprocess 12 failure for convenience. If only one shard is to be run, a single subprocess
13 is started for that shard and the output is identical to gtest's output. 13 is started for that shard and the output is identical to gtest's output.
14 """ 14 """
15 15
16 16
17 import cStringIO 17 import cStringIO
18 import itertools 18 import itertools
19 import optparse 19 import optparse
20 import os 20 import os
21 import Queue 21 import Queue
22 import random 22 import random
23 import re 23 import re
24 import sys 24 import sys
25 import threading 25 import threading
26 26
27 from xml.dom import minidom
28
27 # Add tools/ to path 29 # Add tools/ to path
28 BASE_PATH = os.path.dirname(os.path.abspath(__file__)) 30 BASE_PATH = os.path.dirname(os.path.abspath(__file__))
29 sys.path.append(os.path.join(BASE_PATH, "..")) 31 sys.path.append(os.path.join(BASE_PATH, ".."))
30 try: 32 try:
31 import find_depot_tools 33 import find_depot_tools
32 # Fixes a bug in Windows where some shards die upon starting 34 # Fixes a bug in Windows where some shards die upon starting
33 # TODO(charleslee): actually fix this bug 35 # TODO(charleslee): actually fix this bug
34 import subprocess2 as subprocess 36 import subprocess2 as subprocess
35 except ImportError: 37 except ImportError:
36 # Unable to find depot_tools, so just use standard subprocess 38 # Unable to find depot_tools, so just use standard subprocess
(...skipping 22 matching lines...) Expand all
59 return int(os.sysconf("SC_NPROCESSORS_ONLN")) 61 return int(os.sysconf("SC_NPROCESSORS_ONLN"))
60 else: 62 else:
61 # OSX 63 # OSX
62 return int(os.popen2("sysctl -n hw.ncpu")[1].read()) 64 return int(os.popen2("sysctl -n hw.ncpu")[1].read())
63 # Windows 65 # Windows
64 return int(os.environ["NUMBER_OF_PROCESSORS"]) 66 return int(os.environ["NUMBER_OF_PROCESSORS"])
65 except ValueError: 67 except ValueError:
66 return SS_DEFAULT_NUM_CORES 68 return SS_DEFAULT_NUM_CORES
67 69
68 70
71 def GetGTestOutput(args):
72 """Extracts gtest_output from the args. Returns none if not present."""
73
74 for arg in args:
75 if '--gtest_output=' in arg:
76 return arg.split('=')[1]
77 return None
78
79
80 def AppendToGTestOutput(gtest_args, value):
81 args = gtest_args[:]
82 current_value = GetGTestOutput(args)
83 if not current_value:
84 return
85
86 current_arg = '--gtest_output=' + current_value
87 args.remove(current_arg)
88 args.append('--gtest_output=' + current_value + value)
89 return args
90
91
92 def RemoveGTestOutput(gtest_args):
93 args = gtest_args[:]
94 current_value = GetGTestOutput(args)
95 if not current_value:
96 return
97
98 args.remove('--gtest_output=' + current_value)
99 return args
100
101
102 def AppendToXML(final_xml, generic_path, shard):
103 """Combine the shard xml file with the final xml file."""
104
105 path = generic_path + str(shard)
106 with open(path) as shard_xml_file:
107 shard_xml = minidom.parse(shard_xml_file)
108
109 if not final_xml:
110 # Out final xml is empty, let's prepopulate it with the first one we see.
111 return shard_xml
112
113 shard_node = shard_xml.documentElement
114 final_node = final_xml.documentElement
115
116 testcases = shard_node.getElementsByTagName('testcase')
117 final_testcases = final_node.getElementsByTagName('testcase')
118 for testcase in testcases:
119 name = testcase.getAttribute('name')
120 classname = testcase.getAttribute('classname')
121 failures = testcase.getElementsByTagName('failure')
122 status = testcase.getAttribute('status')
123 elapsed = testcase.getAttribute('time')
124
125 # don't bother updating the final xml if there is no data.
126 if status == 'notrun':
127 continue
128
129 # Look in our final xml to see if it's there.
130 # There has to be a better way...
131 for final_testcase in final_testcases:
132 final_name = final_testcase.getAttribute('name')
133 final_classname = final_testcase.getAttribute('classname')
134 if final_name == name and final_classname == classname:
135 # We got the same entry.
136 final_testcase.setAttribute('status', status)
137 final_testcase.setAttribute('time', elapsed)
138 for failure in failures:
139 final_testcase.appendChild(failure)
140
141 return final_xml
142
143
69 def RunShard(test, total_shards, index, gtest_args, stdout, stderr): 144 def RunShard(test, total_shards, index, gtest_args, stdout, stderr):
70 """Runs a single test shard in a subprocess. 145 """Runs a single test shard in a subprocess.
71 146
72 Returns: 147 Returns:
73 The Popen object representing the subprocess handle. 148 The Popen object representing the subprocess handle.
74 """ 149 """
75 args = [test] 150 args = [test]
76 args.extend(gtest_args) 151
152 # If there is a gtest_output
153 test_args = AppendToGTestOutput(gtest_args, str(index))
154 args.extend(test_args)
77 env = os.environ.copy() 155 env = os.environ.copy()
78 env["GTEST_TOTAL_SHARDS"] = str(total_shards) 156 env["GTEST_TOTAL_SHARDS"] = str(total_shards)
79 env["GTEST_SHARD_INDEX"] = str(index) 157 env["GTEST_SHARD_INDEX"] = str(index)
80 158
81 # Use a unique log file for each shard 159 # Use a unique log file for each shard
82 # Allows ui_tests to be run in parallel on the same machine 160 # Allows ui_tests to be run in parallel on the same machine
83 env["CHROME_LOG_FILE"] = "chrome_log_%d" % index 161 env["CHROME_LOG_FILE"] = "chrome_log_%d" % index
84 162
85 return subprocess.Popen( 163 return subprocess.Popen(
86 args, stdout=stdout, 164 args, stdout=stdout,
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after
275 worker = ShardRunner( 353 worker = ShardRunner(
276 self, counter, test_start, test_ok, test_fail) 354 self, counter, test_start, test_ok, test_fail)
277 worker.start() 355 worker.start()
278 workers.append(worker) 356 workers.append(worker)
279 if self.original_order: 357 if self.original_order:
280 for worker in workers: 358 for worker in workers:
281 worker.join() 359 worker.join()
282 else: 360 else:
283 self.WaitForShards() 361 self.WaitForShards()
284 362
363 # All the shards are done. Merge all the XML files and generate the
364 # main one.
365 output_arg = GetGTestOutput(self.gtest_args)
366 if output_arg:
367 xml, xml_path = output_arg.split(':', 1)
368 assert(xml == 'xml')
369 final_xml = None
370 for i in range(start_point, start_point + self.num_shards_to_run):
371 final_xml = AppendToXML(final_xml, xml_path, i)
372 with open(xml_path, 'w') as final_file:
373 final_xml.writexml(final_file)
374
285 num_failed = len(self.failed_shards) 375 num_failed = len(self.failed_shards)
286 if num_failed > 0: 376 if num_failed > 0:
287 self.failed_shards.sort() 377 self.failed_shards.sort()
288 self.WriteText(sys.stdout, 378 self.WriteText(sys.stdout,
289 "\nFAILED SHARDS: %s\n" % str(self.failed_shards), 379 "\nFAILED SHARDS: %s\n" % str(self.failed_shards),
290 "\x1b[1;5;31m") 380 "\x1b[1;5;31m")
291 else: 381 else:
292 self.WriteText(sys.stdout, "\nALL SHARDS PASSED!\n", "\x1b[1;5;32m") 382 self.WriteText(sys.stdout, "\nALL SHARDS PASSED!\n", "\x1b[1;5;32m")
293 self.PrintSummary(self.failed_tests) 383 self.PrintSummary(self.failed_tests)
294 if self.retry_percent < 0: 384 if self.retry_percent < 0:
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
384 sys.stdout.write("\nNOT RETRYING FAILED TESTS (too many failed)\n") 474 sys.stdout.write("\nNOT RETRYING FAILED TESTS (too many failed)\n")
385 return 1 475 return 1
386 self.WriteText(sys.stdout, "\nRETRYING FAILED TESTS:\n", "\x1b[1;5;33m") 476 self.WriteText(sys.stdout, "\nRETRYING FAILED TESTS:\n", "\x1b[1;5;33m")
387 sharded_description = re.compile(r": (?:\d+>)?(.*)") 477 sharded_description = re.compile(r": (?:\d+>)?(.*)")
388 gtest_filters = [sharded_description.search(line).group(1) 478 gtest_filters = [sharded_description.search(line).group(1)
389 for line in self.failed_tests] 479 for line in self.failed_tests]
390 failed_retries = [] 480 failed_retries = []
391 481
392 for test_filter in gtest_filters: 482 for test_filter in gtest_filters:
393 args = [self.test, "--gtest_filter=" + test_filter] 483 args = [self.test, "--gtest_filter=" + test_filter]
394 args.extend(self.gtest_args) 484 # Don't update the xml output files during retry.
485 stripped_gtests_args = RemoveGTestOutput(self.gtest_args)
486 args.extend(stripped_gtests_args)
395 rerun = subprocess.Popen(args) 487 rerun = subprocess.Popen(args)
396 rerun.wait() 488 rerun.wait()
397 if rerun.returncode != 0: 489 if rerun.returncode != 0:
398 failed_retries.append(test_filter) 490 failed_retries.append(test_filter)
399 491
400 self.WriteText(sys.stdout, "RETRY RESULTS:\n", "\x1b[1;5;33m") 492 self.WriteText(sys.stdout, "RETRY RESULTS:\n", "\x1b[1;5;33m")
401 self.PrintSummary(failed_retries) 493 self.PrintSummary(failed_retries)
402 return len(failed_retries) > 0 494 return len(failed_retries) > 0
403 495
404 def PrintSummary(self, failed_tests): 496 def PrintSummary(self, failed_tests):
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after
528 # shard and run the whole test 620 # shard and run the whole test
529 ss = ShardingSupervisor( 621 ss = ShardingSupervisor(
530 args[0], num_shards_to_run, num_runs, options.color, 622 args[0], num_shards_to_run, num_runs, options.color,
531 options.original_order, options.prefix, options.retry_percent, 623 options.original_order, options.prefix, options.retry_percent,
532 options.timeout, options.total_slaves, options.slave_index, gtest_args) 624 options.timeout, options.total_slaves, options.slave_index, gtest_args)
533 return ss.ShardTest() 625 return ss.ShardTest()
534 626
535 627
536 if __name__ == "__main__": 628 if __name__ == "__main__":
537 sys.exit(main()) 629 sys.exit(main())
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698