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

Side by Side Diff: tools/testrunner/local/execution.py

Issue 1469833002: [test-runner] Move test case processing beyond the multi-process boundary. (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Review Created 5 years 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
« no previous file with comments | « tools/run-tests.py ('k') | tools/testrunner/local/pool.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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):
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 sent over the multi-process boundary.
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 # Retrieve a new suite object on the worker-process side. The original
143 # suite object isn't pickled.
144 self.test.SetSuiteObject(process_context.suites)
145 instr = _GetInstructions(self.test, process_context.context)
146
147 start_time = time.time()
148 if instr.dep_command is not None:
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
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
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
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)
OLDNEW
« no previous file with comments | « tools/run-tests.py ('k') | tools/testrunner/local/pool.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698