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

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

Issue 7650012: Recognize crashed tests as failures in sharding_supervisor (Closed) Base URL: http://git.chromium.org/git/chromium.git@trunk
Patch Set: Got rid of unneeded return Created 9 years, 4 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 | Annotate | Revision Log
« 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
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
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
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
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
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
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