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 |