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

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
M-A Ruel 2012/04/13 20:07:44 "return None" is not strictly necessary.
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
89 new_value = current_value + value
90 new_arg = '--gtest_output=' + new_value
91 args.append(new_arg)
M-A Ruel 2012/04/13 20:07:44 args.append('--gtest_output=' + current_value + va
nsylvain 2012/04/13 20:30:58 Done.
92
93 return args
94
95
96 def RemoveGTestOutput(gtest_args):
97 args = gtest_args[:]
98 current_value = GetGTestOutput(args)
99 if not current_value:
100 return
101
102 current_arg = '--gtest_output=' + current_value
M-A Ruel 2012/04/13 20:07:44 args.remove('--gtest_output=' + current_value) is
nsylvain 2012/04/13 20:30:58 Done.
103 args.remove(current_arg)
104
105 return args
106
107
108 def AppendToXML(final_xml, generic_path, shard):
M-A Ruel 2012/04/13 20:07:44 Can you add a one-liner docstring?
nsylvain 2012/04/13 20:30:58 Done.
109 path = generic_path + str(shard)
110 shard_xml_file = open(path)
M-A Ruel 2012/04/13 20:07:44 with open(path) as shard_xml_file:
nsylvain 2012/04/13 20:30:58 Done.
111 shard_xml = minidom.parse(shard_xml_file)
112 if not final_xml:
113 # Out final xml is empty, let's prepopulate it with the first one we see.
114 return shard_xml
115
116 shard_node = shard_xml.documentElement
117 final_node = final_xml.documentElement
118
119 testcases = shard_node.getElementsByTagName('testcase')
M-A Ruel 2012/04/13 20:07:44 I still think you should have used ElementTree ins
120 final_testcases = final_node.getElementsByTagName('testcase')
121 for testcase in testcases:
122 name = testcase.getAttribute('name')
123 classname = testcase.getAttribute('classname')
124 failures = testcase.getElementsByTagName('failure')
125 status = testcase.getAttribute('status')
126 elapsed = testcase.getAttribute('time')
127
128 # don't bother updating the final xml if there is no data.
129 if status == 'notrun':
130 continue
131
132 # Look in our final xml to see if it's there.
133 # There has to be a better way...
134 for final_testcase in final_testcases:
135 final_name = final_testcase.getAttribute('name')
136 final_classname = final_testcase.getAttribute('classname')
137 if final_name == name and final_classname == classname:
138 # We got the same entry.
139 final_testcase.setAttribute('status', status)
140 final_testcase.setAttribute('time', elapsed)
141 for failure in failures:
142 final_testcase.appendChild(failure)
143
144 return final_xml
145
146
69 def RunShard(test, total_shards, index, gtest_args, stdout, stderr): 147 def RunShard(test, total_shards, index, gtest_args, stdout, stderr):
70 """Runs a single test shard in a subprocess. 148 """Runs a single test shard in a subprocess.
71 149
72 Returns: 150 Returns:
73 The Popen object representing the subprocess handle. 151 The Popen object representing the subprocess handle.
74 """ 152 """
75 args = [test] 153 args = [test]
76 args.extend(gtest_args) 154
155 # If there is a gtest_output
156 test_args = AppendToGTestOutput(gtest_args, str(index))
157 args.extend(test_args)
77 env = os.environ.copy() 158 env = os.environ.copy()
78 env["GTEST_TOTAL_SHARDS"] = str(total_shards) 159 env["GTEST_TOTAL_SHARDS"] = str(total_shards)
79 env["GTEST_SHARD_INDEX"] = str(index) 160 env["GTEST_SHARD_INDEX"] = str(index)
80 161
81 # Use a unique log file for each shard 162 # Use a unique log file for each shard
82 # Allows ui_tests to be run in parallel on the same machine 163 # Allows ui_tests to be run in parallel on the same machine
83 env["CHROME_LOG_FILE"] = "chrome_log_%d" % index 164 env["CHROME_LOG_FILE"] = "chrome_log_%d" % index
84 165
85 return subprocess.Popen( 166 return subprocess.Popen(
86 args, stdout=stdout, 167 args, stdout=stdout,
(...skipping 188 matching lines...) Expand 10 before | Expand all | Expand 10 after
275 worker = ShardRunner( 356 worker = ShardRunner(
276 self, counter, test_start, test_ok, test_fail) 357 self, counter, test_start, test_ok, test_fail)
277 worker.start() 358 worker.start()
278 workers.append(worker) 359 workers.append(worker)
279 if self.original_order: 360 if self.original_order:
280 for worker in workers: 361 for worker in workers:
281 worker.join() 362 worker.join()
282 else: 363 else:
283 self.WaitForShards() 364 self.WaitForShards()
284 365
366 # All the shards are done. Merge all the XML files and generate the
367 # main one.
368 output_arg = GetGTestOutput(self.gtest_args)
369 if output_arg:
370 xml, xml_path = output_arg.split(':')
371 assert(xml == 'xml')
372 final_xml = None
373 for i in range(start_point, start_point + self.num_shards_to_run):
374 final_xml = AppendToXML(final_xml, xml_path, i)
M-A Ruel 2012/04/13 20:07:44 can you do the str(i) here instead of at line 109?
375 final_file = open(xml_path, 'w')
M-A Ruel 2012/04/13 20:07:44 with open(xml_path, 'w') as final_file: final_xm
nsylvain 2012/04/13 20:30:58 Done.
376 final_xml.writexml(final_file)
377 final_file.close()
378
285 num_failed = len(self.failed_shards) 379 num_failed = len(self.failed_shards)
286 if num_failed > 0: 380 if num_failed > 0:
287 self.failed_shards.sort() 381 self.failed_shards.sort()
288 self.WriteText(sys.stdout, 382 self.WriteText(sys.stdout,
289 "\nFAILED SHARDS: %s\n" % str(self.failed_shards), 383 "\nFAILED SHARDS: %s\n" % str(self.failed_shards),
290 "\x1b[1;5;31m") 384 "\x1b[1;5;31m")
291 else: 385 else:
292 self.WriteText(sys.stdout, "\nALL SHARDS PASSED!\n", "\x1b[1;5;32m") 386 self.WriteText(sys.stdout, "\nALL SHARDS PASSED!\n", "\x1b[1;5;32m")
293 self.PrintSummary(self.failed_tests) 387 self.PrintSummary(self.failed_tests)
294 if self.retry_percent < 0: 388 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") 478 sys.stdout.write("\nNOT RETRYING FAILED TESTS (too many failed)\n")
385 return 1 479 return 1
386 self.WriteText(sys.stdout, "\nRETRYING FAILED TESTS:\n", "\x1b[1;5;33m") 480 self.WriteText(sys.stdout, "\nRETRYING FAILED TESTS:\n", "\x1b[1;5;33m")
387 sharded_description = re.compile(r": (?:\d+>)?(.*)") 481 sharded_description = re.compile(r": (?:\d+>)?(.*)")
388 gtest_filters = [sharded_description.search(line).group(1) 482 gtest_filters = [sharded_description.search(line).group(1)
389 for line in self.failed_tests] 483 for line in self.failed_tests]
390 failed_retries = [] 484 failed_retries = []
391 485
392 for test_filter in gtest_filters: 486 for test_filter in gtest_filters:
393 args = [self.test, "--gtest_filter=" + test_filter] 487 args = [self.test, "--gtest_filter=" + test_filter]
394 args.extend(self.gtest_args) 488 # Don't update the xml output files during retry.
489 stripped_gtests_args = RemoveGTestOutput(self.gtest_args)
490 args.extend(stripped_gtests_args)
395 rerun = subprocess.Popen(args) 491 rerun = subprocess.Popen(args)
396 rerun.wait() 492 rerun.wait()
397 if rerun.returncode != 0: 493 if rerun.returncode != 0:
398 failed_retries.append(test_filter) 494 failed_retries.append(test_filter)
399 495
400 self.WriteText(sys.stdout, "RETRY RESULTS:\n", "\x1b[1;5;33m") 496 self.WriteText(sys.stdout, "RETRY RESULTS:\n", "\x1b[1;5;33m")
401 self.PrintSummary(failed_retries) 497 self.PrintSummary(failed_retries)
402 return len(failed_retries) > 0 498 return len(failed_retries) > 0
403 499
404 def PrintSummary(self, failed_tests): 500 def PrintSummary(self, failed_tests):
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after
528 # shard and run the whole test 624 # shard and run the whole test
529 ss = ShardingSupervisor( 625 ss = ShardingSupervisor(
530 args[0], num_shards_to_run, num_runs, options.color, 626 args[0], num_shards_to_run, num_runs, options.color,
531 options.original_order, options.prefix, options.retry_percent, 627 options.original_order, options.prefix, options.retry_percent,
532 options.timeout, options.total_slaves, options.slave_index, gtest_args) 628 options.timeout, options.total_slaves, options.slave_index, gtest_args)
533 return ss.ShardTest() 629 return ss.ShardTest()
534 630
535 631
536 if __name__ == "__main__": 632 if __name__ == "__main__":
537 sys.exit(main()) 633 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