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

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: 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 test_timeout):
85 """Inits ShardRunner with a supervisor and counter.""" 86 """Inits ShardRunner with a supervisor and counter."""
86 threading.Thread.__init__(self) 87 threading.Thread.__init__(self)
87 self.supervisor = supervisor 88 self.supervisor = supervisor
88 self.counter = counter 89 self.counter = counter
90 self.test_start = test_start
91 self.test_ok = test_ok
89 self.test_fail = test_fail 92 self.test_fail = test_fail
90 self.test_timeout = test_timeout 93 self.test_timeout = test_timeout
94 self.current_test = ""
91 95
92 def SearchForFailure(self, regex, prefix, line, description): 96 def ReportFailure(self, description, prefix, test_name):
93 results = regex.search(line) 97 log_line = "%s: %s%s\n" % (description, prefix, test_name)
98 self.supervisor.LogLineFailure(log_line)
99
100 def ProcessLine(self, prefix, line):
101 results = self.test_start.search(line)
94 if results: 102 if results:
95 log_line = "%s: %s%s\n" % (description, prefix, results.group(1)) 103 if self.current_test:
96 self.supervisor.LogLineFailure(log_line) 104 self.ReportFailure("CRASHED", prefix, results.group(1))
97 return True 105 self.current_test = results.group(1)
98 return False 106 return
107
108 results = self.test_ok.search(line)
109 if results:
110 self.current_test = ""
111 return
112
113 results = self.test_fail.search(line)
114 if results:
115 self.ReportFailure("FAILED", prefix, results.group(1))
116 self.current_test = ""
117 return
118
119 results = self.test_timeout.search(line)
Paweł Hajdan Jr. 2011/08/15 17:33:17 Don't rely on that line to be present. It's fragil
clee 2011/08/15 18:07:09 It's what gtest_command uses to detect timeouts. T
Paweł Hajdan Jr. 2011/08/16 17:36:07 And guess what? It's broken.
120 if results:
121 self.ReportFailure("TIMEOUT", prefix, results.group(1))
122 self.current_test = ""
99 123
100 def run(self): 124 def run(self):
101 """Runs shards and outputs the results. 125 """Runs shards and outputs the results.
102 126
103 Gets the next shard index from the supervisor, runs it in a subprocess, 127 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 128 and collects the output. Each line is prefixed with 'N>', where N is the
105 current shard index. 129 current shard index.
106 """ 130 """
107 while True: 131 while True:
108 try: 132 try:
109 index = self.counter.get_nowait() 133 index = self.counter.get_nowait()
110 except Queue.Empty: 134 except Queue.Empty:
111 break 135 break
112 prefix = "%i>" % index 136 prefix = "%i>" % index
113 chars = StringIO() 137 chars = StringIO()
114 shard_running = True 138 shard_running = True
115 shard = RunShard( 139 shard = RunShard(
116 self.supervisor.test, self.supervisor.num_shards, index, 140 self.supervisor.test, self.supervisor.num_shards, index,
117 self.supervisor.gtest_args, subprocess.PIPE, subprocess.STDOUT) 141 self.supervisor.gtest_args, subprocess.PIPE, subprocess.STDOUT)
118 while shard_running: 142 while shard_running:
119 char = shard.stdout.read(1) 143 char = shard.stdout.read(1)
120 if not char and shard.poll() is not None: 144 if not char and shard.poll() is not None:
121 shard_running = False 145 shard_running = False
122 chars.write(char) 146 chars.write(char)
123 if char == "\n" or not shard_running: 147 if char == "\n" or not shard_running:
124 line = chars.getvalue() 148 line = chars.getvalue()
125 if not line and not shard_running: 149 if not line and not shard_running:
126 break 150 break
127 if not self.SearchForFailure( 151 self.ProcessLine(prefix, line)
128 self.test_fail, prefix, line, "FAILED"):
129 self.SearchForFailure(self.test_timeout, prefix, line, "TIMEOUT")
130 line = prefix + line 152 line = prefix + line
131 self.supervisor.LogOutputLine(index, line) 153 self.supervisor.LogOutputLine(index, line)
132 chars.close() 154 chars.close()
133 chars = StringIO() 155 chars = StringIO()
134 self.supervisor.ShardIndexCompleted(index) 156 self.supervisor.ShardIndexCompleted(index)
135 if shard.returncode != 0: 157 if shard.returncode != 0:
136 self.supervisor.LogShardFailure(index) 158 self.supervisor.LogShardFailure(index)
137 159
138 160
139 class ShardingSupervisor(object): 161 class ShardingSupervisor(object):
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
178 """ 200 """
179 201
180 # Regular expressions for parsing GTest logs. Test names look like 202 # Regular expressions for parsing GTest logs. Test names look like
181 # SomeTestCase.SomeTest 203 # SomeTestCase.SomeTest
182 # SomeName/SomeTestCase.SomeTest/1 204 # SomeName/SomeTestCase.SomeTest/1
183 # This regex also matches SomeName.SomeTest/1 and 205 # This regex also matches SomeName.SomeTest/1 and
184 # SomeName/SomeTestCase.SomeTest, which should be harmless. 206 # SomeName/SomeTestCase.SomeTest, which should be harmless.
185 test_name_regex = r"((\w+/)?\w+\.\w+(/\d+)?)" 207 test_name_regex = r"((\w+/)?\w+\.\w+(/\d+)?)"
186 208
187 # Regex for filtering out ANSI escape codes when using color. 209 # Regex for filtering out ANSI escape codes when using color.
188 ansi_code_regex = r"(?:\x1b\[.*?[a-zA-Z])?" 210 ansi_regex = r"(?:\x1b\[.*?[a-zA-Z])?"
189 211
212 test_start = re.compile(
213 ansi_regex + "\[\s+RUN\s+\] " + ansi_regex + test_name_regex)
M-A Ruel 2011/08/15 15:46:08 Use r"".
clee 2011/08/15 18:07:09 Done.
214 test_ok = re.compile(
215 ansi_regex + "\[\s+OK\s+\] " + ansi_regex + test_name_regex)
190 test_fail = re.compile( 216 test_fail = re.compile(
191 ansi_code_regex + "\[\s+FAILED\s+\] " + ansi_code_regex + 217 ansi_regex + "\[\s+FAILED\s+\] " + ansi_regex + test_name_regex)
192 test_name_regex)
193 test_timeout = re.compile( 218 test_timeout = re.compile(
194 "Test timeout \([0-9]+ ms\) exceeded for " + test_name_regex) 219 "Test timeout \([0-9]+ ms\) exceeded for " + test_name_regex)
195 220
196 workers = [] 221 workers = []
197 counter = Queue.Queue() 222 counter = Queue.Queue()
198 for i in range(self.num_shards): 223 for i in range(self.num_shards):
199 counter.put(i) 224 counter.put(i)
200 225
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): 226 for i in range(self.num_runs):
206 worker = ShardRunner(self, counter, test_fail, test_timeout) 227 worker = ShardRunner(
228 self, counter, test_start, test_ok, test_fail, test_timeout)
207 worker.start() 229 worker.start()
208 workers.append(worker) 230 workers.append(worker)
209 if self.reorder: 231 if self.reorder:
210 self.WaitForShards() 232 self.WaitForShards()
211 else: 233 else:
212 for worker in workers: 234 for worker in workers:
213 worker.join() 235 worker.join()
214 236
215 # Re-enable stdout buffering. 237 return self.PrintSummary(self.failure_log)
216 sys.stdout = sys.__stdout__
217
218 return self.PrintSummary()
219 238
220 def LogLineFailure(self, line): 239 def LogLineFailure(self, line):
221 """Saves a line in the failure log to be printed at the end. 240 """Saves a line in the failure log to be printed at the end.
222 241
223 Args: 242 Args:
224 line: The line to save in the failure_log. 243 line: The line to save in the failure_log.
225 """ 244 """
226 if line not in self.failure_log: 245 if line not in self.failure_log:
227 self.failure_log.append(line) 246 self.failure_log.append(line)
228 247
(...skipping 15 matching lines...) Expand all
244 263
245 def LogOutputLine(self, index, line): 264 def LogOutputLine(self, index, line):
246 if self.reorder: 265 if self.reorder:
247 self.shard_output[index].put(line) 266 self.shard_output[index].put(line)
248 else: 267 else:
249 sys.stdout.write(line) 268 sys.stdout.write(line)
250 269
251 def ShardIndexCompleted(self, index): 270 def ShardIndexCompleted(self, index):
252 self.shard_output[index].put(self.SHARD_COMPLETED) 271 self.shard_output[index].put(self.SHARD_COMPLETED)
253 272
254 def PrintSummary(self): 273 def PrintSummary(self, failed_tests):
255 """Prints a summary of the test results. 274 """Prints a summary of the test results.
256 275
257 If any shards had failing tests, the list is sorted and printed. Then all 276 If any shards had failing tests, the list is sorted and printed. Then all
258 the lines that indicate a test failure are reproduced. 277 the lines that indicate a test failure are reproduced.
259 278
260 Returns: 279 Returns:
261 The number of shards that had failing tests. 280 The number of shards that had failing tests.
262 """ 281 """
263 sys.stderr.write("\n") 282 sys.stderr.write("\n")
264 num_failed = len(self.failed_shards) 283 num_failed = len(self.failed_shards)
265 if num_failed > 0: 284 if num_failed > 0:
266 self.failed_shards.sort() 285 self.failed_shards.sort()
267 if self.color: 286 self.WriteText(sys.stderr,
268 sys.stderr.write("\x1b[1;5;31m") 287 "FAILED SHARDS: %s\n" % str(self.failed_shards),
269 sys.stderr.write("SHARDS THAT FAILED: %s\n" % str(self.failed_shards)) 288 "\x1b[1;5;31m")
270 else: 289 else:
271 if self.color: 290 self.WriteText(sys.stderr, "ALL SHARDS PASSED!\n", "\x1b[1;5;32m")
272 sys.stderr.write("\x1b[1;5;32m") 291 if failed_tests:
273 sys.stderr.write("ALL SHARDS PASSED!\n") 292 self.WriteText(sys.stderr, "FAILED TESTS:\n", "\x1b[1;5;31m")
274 if self.failure_log: 293 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) 294 sys.stderr.write(line)
282 if self.color: 295 if self.color:
283 sys.stderr.write("\x1b[m") 296 sys.stderr.write("\x1b[m")
284 return num_failed 297 return num_failed
285 298
299 def WriteText(self, pipe, text, ansi):
300 if self.color:
301 pipe.write(ansi)
302 pipe.write(text)
303 if self.color:
304 pipe.write("\x1b[m")
305
286 306
287 def main(): 307 def main():
288 parser = optparse.OptionParser(usage=SS_USAGE) 308 parser = optparse.OptionParser(usage=SS_USAGE)
289 parser.add_option( 309 parser.add_option(
290 "-n", "--shards_per_core", type="int", default=SS_DEFAULT_SHARDS_PER_CORE, 310 "-n", "--shards_per_core", type="int", default=SS_DEFAULT_SHARDS_PER_CORE,
291 help="number of shards to generate per CPU") 311 help="number of shards to generate per CPU")
292 parser.add_option( 312 parser.add_option(
293 "-r", "--runs_per_core", type="int", default=SS_DEFAULT_RUNS_PER_CORE, 313 "-r", "--runs_per_core", type="int", default=SS_DEFAULT_RUNS_PER_CORE,
294 help="number of shards to run in parallel per CPU") 314 help="number of shards to run in parallel per CPU")
295 parser.add_option( 315 parser.add_option(
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
343 363
344 # shard and run the whole test 364 # shard and run the whole test
345 ss = ShardingSupervisor(args[0], num_shards, num_runs, options.color, 365 ss = ShardingSupervisor(args[0], num_shards, num_runs, options.color,
346 options.reorder, gtest_args) 366 options.reorder, gtest_args)
347 return ss.ShardTest() 367 return ss.ShardTest()
348 368
349 369
350 if __name__ == "__main__": 370 if __name__ == "__main__":
351 sys.exit(main()) 371 sys.exit(main())
352 372
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