OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |