Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2012 the V8 project authors. All rights reserved. | 1 # Copyright 2012 the V8 project authors. All rights reserved. |
| 2 # Redistribution and use in source and binary forms, with or without | 2 # Redistribution and use in source and binary forms, with or without |
| 3 # modification, are permitted provided that the following conditions are | 3 # modification, are permitted provided that the following conditions are |
| 4 # met: | 4 # met: |
| 5 # | 5 # |
| 6 # * Redistributions of source code must retain the above copyright | 6 # * Redistributions of source code must retain the above copyright |
| 7 # notice, this list of conditions and the following disclaimer. | 7 # notice, this list of conditions and the following disclaimer. |
| 8 # * Redistributions in binary form must reproduce the above | 8 # * Redistributions in binary form must reproduce the above |
| 9 # copyright notice, this list of conditions and the following | 9 # copyright notice, this list of conditions and the following |
| 10 # disclaimer in the documentation and/or other materials provided | 10 # disclaimer in the documentation and/or other materials provided |
| 11 # with the distribution. | 11 # with the distribution. |
| 12 # * Neither the name of Google Inc. nor the names of its | 12 # * Neither the name of Google Inc. nor the names of its |
| 13 # contributors may be used to endorse or promote products derived | 13 # contributors may be used to endorse or promote products derived |
| 14 # from this software without specific prior written permission. | 14 # from this software without specific prior written permission. |
| 15 # | 15 # |
| 16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 16 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 17 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 17 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 18 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 18 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 19 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 19 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 20 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 20 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 23 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 23 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 24 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 24 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 25 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 26 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | 27 |
| 28 | 28 |
| 29 import collections | |
| 29 import os | 30 import os |
| 30 import shutil | 31 import shutil |
| 31 import sys | 32 import sys |
| 32 import time | 33 import time |
| 33 | 34 |
| 34 from pool import Pool | 35 from pool import Pool |
| 35 from . import commands | 36 from . import commands |
| 36 from . import perfdata | 37 from . import perfdata |
| 37 from . import statusfile | 38 from . import statusfile |
| 39 from . import testsuite | |
| 38 from . import utils | 40 from . import utils |
| 39 | 41 |
| 40 | 42 |
| 41 class Job(object): | 43 # Base dir of the v8 checkout. |
| 44 BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname( | |
| 45 os.path.abspath(__file__))))) | |
| 46 TEST_DIR = os.path.join(BASE_DIR, "test") | |
| 47 | |
| 48 | |
| 49 class Instructions(object): | |
| 42 def __init__(self, command, dep_command, test_id, timeout, verbose): | 50 def __init__(self, command, dep_command, test_id, timeout, verbose): |
| 43 self.command = command | 51 self.command = command |
| 44 self.dep_command = dep_command | 52 self.dep_command = dep_command |
| 45 self.id = test_id | 53 self.id = test_id |
| 46 self.timeout = timeout | 54 self.timeout = timeout |
| 47 self.verbose = verbose | 55 self.verbose = verbose |
| 48 | 56 |
| 49 | 57 |
| 50 def RunTest(job): | 58 # Structure that keeps global information per worker process. |
| 51 start_time = time.time() | 59 ProcessContext = collections.namedtuple( |
| 52 if job.dep_command is not None: | 60 "process_context", ["suites", "context"]) |
| 53 dep_output = commands.Execute(job.dep_command, job.verbose, job.timeout) | 61 |
| 54 # TODO(jkummerow): We approximate the test suite specific function | 62 |
| 55 # IsFailureOutput() by just checking the exit code here. Currently | 63 def MakeProcessContext(context): |
| 56 # only cctests define dependencies, for which this simplification is | 64 """Generate a process-local context. |
| 57 # correct. | 65 |
| 58 if dep_output.exit_code != 0: | 66 This reloads all suites per process and stores the global context. |
| 59 return (job.id, dep_output, time.time() - start_time) | 67 |
| 60 output = commands.Execute(job.command, job.verbose, job.timeout) | 68 Args: |
| 61 return (job.id, output, time.time() - start_time) | 69 context: The global context from the test runner. |
| 70 """ | |
| 71 suite_paths = utils.GetSuitePaths(TEST_DIR) | |
| 72 suites = {} | |
| 73 for root in suite_paths: | |
| 74 # Don't reinitialize global state as this is concurrently called from | |
| 75 # different processes. | |
| 76 suite = testsuite.TestSuite.LoadTestSuite( | |
| 77 os.path.join(TEST_DIR, root), global_init=False) | |
| 78 if suite: | |
| 79 suites[suite.name] = suite | |
| 80 return ProcessContext(suites, context) | |
| 81 | |
| 82 | |
| 83 def GetCommand(test, context): | |
|
Michael Achenbach
2015/11/27 09:39:41
The next two methods are the old GetCommand and Ge
Michael Achenbach
2015/11/27 10:58:29
Done.
| |
| 84 d8testflag = [] | |
| 85 shell = test.suite.shell() | |
| 86 if shell == "d8": | |
| 87 d8testflag = ["--test"] | |
| 88 if utils.IsWindows(): | |
| 89 shell += ".exe" | |
| 90 if context.random_seed: | |
| 91 d8testflag += ["--random-seed=%s" % context.random_seed] | |
| 92 cmd = (context.command_prefix + | |
| 93 [os.path.abspath(os.path.join(context.shell_dir, shell))] + | |
| 94 d8testflag + | |
| 95 test.suite.GetFlagsForTestCase(test, context) + | |
| 96 context.extra_flags) | |
| 97 return cmd | |
| 98 | |
| 99 | |
| 100 def _GetInstructions(test, context): | |
| 101 command = GetCommand(test, context) | |
| 102 timeout = context.timeout | |
| 103 if ("--stress-opt" in test.flags or | |
| 104 "--stress-opt" in context.mode_flags or | |
| 105 "--stress-opt" in context.extra_flags): | |
| 106 timeout *= 4 | |
| 107 if "--noenable-vfp3" in context.extra_flags: | |
| 108 timeout *= 2 | |
| 109 # FIXME(machenbach): Make this more OO. Don't expose default outcomes or | |
| 110 # the like. | |
| 111 if statusfile.IsSlow(test.outcomes or [statusfile.PASS]): | |
| 112 timeout *= 2 | |
| 113 if test.dependency is not None: | |
| 114 dep_command = [ c.replace(test.path, test.dependency) for c in command ] | |
| 115 else: | |
| 116 dep_command = None | |
| 117 return Instructions( | |
| 118 command, dep_command, test.id, timeout, context.verbose) | |
| 119 | |
| 120 | |
| 121 class Job(object): | |
| 122 """Stores data to be send over the multi-process boundary. | |
|
Jakob Kummerow
2015/11/27 10:09:00
nit: s/send/sent/
| |
| 123 | |
| 124 All contained fields will be pickled/unpickled. | |
| 125 """ | |
| 126 | |
| 127 def Run(self, process_context): | |
| 128 """Executes the job. | |
| 129 | |
| 130 Args: | |
| 131 process_context: Process-local information that is initialized by the | |
| 132 executing worker. | |
| 133 """ | |
| 134 raise NotImplementedError() | |
| 135 | |
| 136 | |
| 137 class TestJob(Job): | |
| 138 def __init__(self, test): | |
| 139 self.test = test | |
| 140 | |
| 141 def Run(self, process_context): | |
| 142 start_time = time.time() | |
|
Jakob Kummerow
2015/11/27 10:09:00
It doesn't matter much, but I'd move this to its o
Michael Achenbach
2015/11/27 10:58:29
Done.
| |
| 143 # Retrieve a new suite object on the worker-process side. The original | |
| 144 # suite object isn't pickled. | |
| 145 self.test.SetSuiteObject(process_context.suites) | |
| 146 instr = _GetInstructions(self.test, process_context.context) | |
| 147 | |
| 148 if instr.dep_command is not None: | |
|
Michael Achenbach
2015/11/27 09:39:41
This part is a 1:1 copy from the old RunTest.
| |
| 149 dep_output = commands.Execute( | |
| 150 instr.dep_command, instr.verbose, instr.timeout) | |
| 151 # TODO(jkummerow): We approximate the test suite specific function | |
| 152 # IsFailureOutput() by just checking the exit code here. Currently | |
| 153 # only cctests define dependencies, for which this simplification is | |
| 154 # correct. | |
| 155 if dep_output.exit_code != 0: | |
| 156 return (instr.id, dep_output, time.time() - start_time) | |
| 157 output = commands.Execute(instr.command, instr.verbose, instr.timeout) | |
| 158 return (instr.id, output, time.time() - start_time) | |
| 159 | |
| 160 | |
| 161 def RunTest(job, process_context): | |
| 162 return job.Run(process_context) | |
| 163 | |
| 62 | 164 |
| 63 class Runner(object): | 165 class Runner(object): |
| 64 | 166 |
| 65 def __init__(self, suites, progress_indicator, context): | 167 def __init__(self, suites, progress_indicator, context): |
| 66 self.datapath = os.path.join("out", "testrunner_data") | 168 self.datapath = os.path.join("out", "testrunner_data") |
| 67 self.perf_data_manager = perfdata.GetPerfDataManager( | 169 self.perf_data_manager = perfdata.GetPerfDataManager( |
| 68 context, self.datapath) | 170 context, self.datapath) |
| 69 self.perfdata = self.perf_data_manager.GetStore(context.arch, context.mode) | 171 self.perfdata = self.perf_data_manager.GetStore(context.arch, context.mode) |
| 70 self.perf_failures = False | 172 self.perf_failures = False |
| 71 self.printed_allocations = False | 173 self.printed_allocations = False |
| (...skipping 21 matching lines...) Expand all Loading... | |
| 93 self.crashed = 0 | 195 self.crashed = 0 |
| 94 self.reran_tests = 0 | 196 self.reran_tests = 0 |
| 95 | 197 |
| 96 def _RunPerfSafe(self, fun): | 198 def _RunPerfSafe(self, fun): |
| 97 try: | 199 try: |
| 98 fun() | 200 fun() |
| 99 except Exception, e: | 201 except Exception, e: |
| 100 print("PerfData exception: %s" % e) | 202 print("PerfData exception: %s" % e) |
| 101 self.perf_failures = True | 203 self.perf_failures = True |
| 102 | 204 |
| 103 def _GetJob(self, test): | |
| 104 command = self.GetCommand(test) | |
| 105 timeout = self.context.timeout | |
| 106 if ("--stress-opt" in test.flags or | |
| 107 "--stress-opt" in self.context.mode_flags or | |
| 108 "--stress-opt" in self.context.extra_flags): | |
| 109 timeout *= 4 | |
| 110 if "--noenable-vfp3" in self.context.extra_flags: | |
| 111 timeout *= 2 | |
| 112 # FIXME(machenbach): Make this more OO. Don't expose default outcomes or | |
| 113 # the like. | |
| 114 if statusfile.IsSlow(test.outcomes or [statusfile.PASS]): | |
| 115 timeout *= 2 | |
| 116 if test.dependency is not None: | |
| 117 dep_command = [ c.replace(test.path, test.dependency) for c in command ] | |
| 118 else: | |
| 119 dep_command = None | |
| 120 return Job(command, dep_command, test.id, timeout, self.context.verbose) | |
| 121 | |
| 122 def _MaybeRerun(self, pool, test): | 205 def _MaybeRerun(self, pool, test): |
| 123 if test.run <= self.context.rerun_failures_count: | 206 if test.run <= self.context.rerun_failures_count: |
| 124 # Possibly rerun this test if its run count is below the maximum per | 207 # Possibly rerun this test if its run count is below the maximum per |
| 125 # test. <= as the flag controls reruns not including the first run. | 208 # test. <= as the flag controls reruns not including the first run. |
| 126 if test.run == 1: | 209 if test.run == 1: |
| 127 # Count the overall number of reran tests on the first rerun. | 210 # Count the overall number of reran tests on the first rerun. |
| 128 if self.reran_tests < self.context.rerun_failures_max: | 211 if self.reran_tests < self.context.rerun_failures_max: |
| 129 self.reran_tests += 1 | 212 self.reran_tests += 1 |
| 130 else: | 213 else: |
| 131 # Don't rerun this if the overall number of rerun tests has been | 214 # Don't rerun this if the overall number of rerun tests has been |
| 132 # reached. | 215 # reached. |
| 133 return | 216 return |
| 134 if test.run >= 2 and test.duration > self.context.timeout / 20.0: | 217 if test.run >= 2 and test.duration > self.context.timeout / 20.0: |
| 135 # Rerun slow tests at most once. | 218 # Rerun slow tests at most once. |
| 136 return | 219 return |
| 137 | 220 |
| 138 # Rerun this test. | 221 # Rerun this test. |
| 139 test.duration = None | 222 test.duration = None |
| 140 test.output = None | 223 test.output = None |
| 141 test.run += 1 | 224 test.run += 1 |
| 142 pool.add([self._GetJob(test)]) | 225 pool.add([TestJob(test)]) |
| 143 self.remaining += 1 | 226 self.remaining += 1 |
| 144 self.total += 1 | 227 self.total += 1 |
| 145 | 228 |
| 146 def _ProcessTestNormal(self, test, result, pool): | 229 def _ProcessTestNormal(self, test, result, pool): |
| 147 self.indicator.AboutToRun(test) | 230 self.indicator.AboutToRun(test) |
| 148 test.output = result[1] | 231 test.output = result[1] |
| 149 test.duration = result[2] | 232 test.duration = result[2] |
| 150 has_unexpected_output = test.suite.HasUnexpectedOutput(test) | 233 has_unexpected_output = test.suite.HasUnexpectedOutput(test) |
| 151 if has_unexpected_output: | 234 if has_unexpected_output: |
| 152 self.failed.append(test) | 235 self.failed.append(test) |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 202 self.indicator.AboutToRun(test) | 285 self.indicator.AboutToRun(test) |
| 203 self.remaining -= 1 | 286 self.remaining -= 1 |
| 204 self.succeeded += 1 | 287 self.succeeded += 1 |
| 205 test.output = result[1] | 288 test.output = result[1] |
| 206 self.indicator.HasRun(test, False) | 289 self.indicator.HasRun(test, False) |
| 207 else: | 290 else: |
| 208 # No difference yet and less than three runs -> add another run and | 291 # No difference yet and less than three runs -> add another run and |
| 209 # remember the output for comparison. | 292 # remember the output for comparison. |
| 210 test.run += 1 | 293 test.run += 1 |
| 211 test.output = result[1] | 294 test.output = result[1] |
| 212 pool.add([self._GetJob(test)]) | 295 pool.add([TestJob(test)]) |
| 213 # Always update the perf database. | 296 # Always update the perf database. |
| 214 return True | 297 return True |
| 215 | 298 |
| 216 def Run(self, jobs): | 299 def Run(self, jobs): |
| 217 self.indicator.Starting() | 300 self.indicator.Starting() |
| 218 self._RunInternal(jobs) | 301 self._RunInternal(jobs) |
| 219 self.indicator.Done() | 302 self.indicator.Done() |
| 220 if self.failed: | 303 if self.failed: |
| 221 return 1 | 304 return 1 |
| 222 elif self.remaining: | 305 elif self.remaining: |
| 223 return 2 | 306 return 2 |
| 224 return 0 | 307 return 0 |
| 225 | 308 |
| 226 def _RunInternal(self, jobs): | 309 def _RunInternal(self, jobs): |
| 227 pool = Pool(jobs) | 310 pool = Pool(jobs) |
| 228 test_map = {} | 311 test_map = {} |
| 229 queued_exception = [None] | 312 queued_exception = [None] |
| 230 def gen_tests(): | 313 def gen_tests(): |
| 231 for test in self.tests: | 314 for test in self.tests: |
| 232 assert test.id >= 0 | 315 assert test.id >= 0 |
| 233 test_map[test.id] = test | 316 test_map[test.id] = test |
| 234 try: | 317 try: |
| 235 yield [self._GetJob(test)] | 318 yield [TestJob(test)] |
| 236 except Exception, e: | 319 except Exception, e: |
| 237 # If this failed, save the exception and re-raise it later (after | 320 # If this failed, save the exception and re-raise it later (after |
| 238 # all other tests have had a chance to run). | 321 # all other tests have had a chance to run). |
| 239 queued_exception[0] = e | 322 queued_exception[0] = e |
| 240 continue | 323 continue |
| 241 try: | 324 try: |
| 242 it = pool.imap_unordered(RunTest, gen_tests()) | 325 it = pool.imap_unordered( |
| 326 fn=RunTest, | |
| 327 gen=gen_tests(), | |
| 328 process_context_fn=MakeProcessContext, | |
| 329 process_context_args=[self.context], | |
| 330 ) | |
| 243 for result in it: | 331 for result in it: |
| 244 if result.heartbeat: | 332 if result.heartbeat: |
| 245 self.indicator.Heartbeat() | 333 self.indicator.Heartbeat() |
| 246 continue | 334 continue |
| 247 test = test_map[result.value[0]] | 335 test = test_map[result.value[0]] |
| 248 if self.context.predictable: | 336 if self.context.predictable: |
| 249 update_perf = self._ProcessTestPredictable(test, result.value, pool) | 337 update_perf = self._ProcessTestPredictable(test, result.value, pool) |
| 250 else: | 338 else: |
| 251 update_perf = self._ProcessTestNormal(test, result.value, pool) | 339 update_perf = self._ProcessTestNormal(test, result.value, pool) |
| 252 if update_perf: | 340 if update_perf: |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 270 not self.total or | 358 not self.total or |
| 271 not self.context.predictable or | 359 not self.context.predictable or |
| 272 self.printed_allocations | 360 self.printed_allocations |
| 273 ) | 361 ) |
| 274 | 362 |
| 275 def _VerbosePrint(self, text): | 363 def _VerbosePrint(self, text): |
| 276 if self.context.verbose: | 364 if self.context.verbose: |
| 277 print text | 365 print text |
| 278 sys.stdout.flush() | 366 sys.stdout.flush() |
| 279 | 367 |
| 280 def GetCommand(self, test): | |
| 281 d8testflag = [] | |
| 282 shell = test.suite.shell() | |
| 283 if shell == "d8": | |
| 284 d8testflag = ["--test"] | |
| 285 if utils.IsWindows(): | |
| 286 shell += ".exe" | |
| 287 if self.context.random_seed: | |
| 288 d8testflag += ["--random-seed=%s" % self.context.random_seed] | |
| 289 cmd = (self.context.command_prefix + | |
| 290 [os.path.abspath(os.path.join(self.context.shell_dir, shell))] + | |
| 291 d8testflag + | |
| 292 test.suite.GetFlagsForTestCase(test, self.context) + | |
| 293 self.context.extra_flags) | |
| 294 return cmd | |
| 295 | |
| 296 | 368 |
| 297 class BreakNowException(Exception): | 369 class BreakNowException(Exception): |
| 298 def __init__(self, value): | 370 def __init__(self, value): |
| 299 self.value = value | 371 self.value = value |
| 300 def __str__(self): | 372 def __str__(self): |
| 301 return repr(self.value) | 373 return repr(self.value) |
| OLD | NEW |