Chromium Code Reviews| 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 |