blob: c9fe54175a3ddd3096c03fb1e398243256daa202 [file] [log] [blame]
Ben Murdochb8a8cc12014-11-26 15:28:44 +00001# Copyright 2012 the V8 project authors. All rights reserved.
2# Redistribution and use in source and binary forms, with or without
3# modification, are permitted provided that the following conditions are
4# met:
5#
6# * Redistributions of source code must retain the above copyright
7# notice, this list of conditions and the following disclaimer.
8# * Redistributions in binary form must reproduce the above
9# copyright notice, this list of conditions and the following
10# disclaimer in the documentation and/or other materials provided
11# with the distribution.
12# * Neither the name of Google Inc. nor the names of its
13# contributors may be used to endorse or promote products derived
14# from this software without specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
20# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
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.
27
28
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000029import collections
Ben Murdochb8a8cc12014-11-26 15:28:44 +000030import os
31import shutil
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000032import sys
Ben Murdochb8a8cc12014-11-26 15:28:44 +000033import time
34
35from pool import Pool
36from . import commands
37from . import perfdata
Emily Bernierd0a1eb72015-03-24 16:35:39 -040038from . import statusfile
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000039from . import testsuite
Ben Murdochb8a8cc12014-11-26 15:28:44 +000040from . import utils
41
42
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000043# Base dir of the v8 checkout.
44BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
45 os.path.abspath(__file__)))))
46TEST_DIR = os.path.join(BASE_DIR, "test")
47
48
49class Instructions(object):
Ben Murdochb8a8cc12014-11-26 15:28:44 +000050 def __init__(self, command, dep_command, test_id, timeout, verbose):
51 self.command = command
52 self.dep_command = dep_command
53 self.id = test_id
54 self.timeout = timeout
55 self.verbose = verbose
56
57
Ben Murdoch4a90d5f2016-03-22 12:00:34 +000058# Structure that keeps global information per worker process.
59ProcessContext = collections.namedtuple(
60 "process_context", ["suites", "context"])
61
62
63def MakeProcessContext(context):
64 """Generate a process-local context.
65
66 This reloads all suites per process and stores the global context.
67
68 Args:
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
83def 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
100def _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
121class 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
137class 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
161def RunTest(job, process_context):
162 return job.Run(process_context)
163
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000164
165class Runner(object):
166
167 def __init__(self, suites, progress_indicator, context):
168 self.datapath = os.path.join("out", "testrunner_data")
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000169 self.perf_data_manager = perfdata.GetPerfDataManager(
170 context, self.datapath)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000171 self.perfdata = self.perf_data_manager.GetStore(context.arch, context.mode)
172 self.perf_failures = False
173 self.printed_allocations = False
174 self.tests = [ t for s in suites for t in s.tests ]
175 if not context.no_sorting:
176 for t in self.tests:
177 t.duration = self.perfdata.FetchPerfData(t) or 1.0
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000178 slow_key = lambda t: statusfile.IsSlow(t.outcomes)
179 self.tests.sort(key=slow_key, reverse=True)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000180 self.tests.sort(key=lambda t: t.duration, reverse=True)
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000181 self._CommonInit(suites, progress_indicator, context)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000182
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000183 def _CommonInit(self, suites, progress_indicator, context):
184 self.total = 0
185 for s in suites:
186 for t in s.tests:
187 t.id = self.total
188 self.total += 1
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000189 self.indicator = progress_indicator
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000190 progress_indicator.SetRunner(self)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000191 self.context = context
192 self.succeeded = 0
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000193 self.remaining = self.total
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000194 self.failed = []
195 self.crashed = 0
196 self.reran_tests = 0
197
198 def _RunPerfSafe(self, fun):
199 try:
200 fun()
201 except Exception, e:
202 print("PerfData exception: %s" % e)
203 self.perf_failures = True
204
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000205 def _MaybeRerun(self, pool, test):
206 if test.run <= self.context.rerun_failures_count:
207 # Possibly rerun this test if its run count is below the maximum per
208 # test. <= as the flag controls reruns not including the first run.
209 if test.run == 1:
210 # Count the overall number of reran tests on the first rerun.
211 if self.reran_tests < self.context.rerun_failures_max:
212 self.reran_tests += 1
213 else:
214 # Don't rerun this if the overall number of rerun tests has been
215 # reached.
216 return
217 if test.run >= 2 and test.duration > self.context.timeout / 20.0:
218 # Rerun slow tests at most once.
219 return
220
221 # Rerun this test.
222 test.duration = None
223 test.output = None
224 test.run += 1
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000225 pool.add([TestJob(test)])
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000226 self.remaining += 1
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000227 self.total += 1
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000228
229 def _ProcessTestNormal(self, test, result, pool):
230 self.indicator.AboutToRun(test)
231 test.output = result[1]
232 test.duration = result[2]
233 has_unexpected_output = test.suite.HasUnexpectedOutput(test)
234 if has_unexpected_output:
235 self.failed.append(test)
236 if test.output.HasCrashed():
237 self.crashed += 1
238 else:
239 self.succeeded += 1
240 self.remaining -= 1
241 # For the indicator, everything that happens after the first run is treated
242 # as unexpected even if it flakily passes in order to include it in the
243 # output.
244 self.indicator.HasRun(test, has_unexpected_output or test.run > 1)
245 if has_unexpected_output:
246 # Rerun test failures after the indicator has processed the results.
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000247 self._VerbosePrint("Attempting to rerun test after failure.")
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000248 self._MaybeRerun(pool, test)
249 # Update the perf database if the test succeeded.
250 return not has_unexpected_output
251
252 def _ProcessTestPredictable(self, test, result, pool):
253 def HasDifferentAllocations(output1, output2):
254 def AllocationStr(stdout):
255 for line in reversed((stdout or "").splitlines()):
256 if line.startswith("### Allocations = "):
257 self.printed_allocations = True
258 return line
259 return ""
260 return (AllocationStr(output1.stdout) != AllocationStr(output2.stdout))
261
262 # Always pass the test duration for the database update.
263 test.duration = result[2]
264 if test.run == 1 and result[1].HasTimedOut():
265 # If we get a timeout in the first run, we are already in an
266 # unpredictable state. Just report it as a failure and don't rerun.
267 self.indicator.AboutToRun(test)
268 test.output = result[1]
269 self.remaining -= 1
270 self.failed.append(test)
271 self.indicator.HasRun(test, True)
272 if test.run > 1 and HasDifferentAllocations(test.output, result[1]):
273 # From the second run on, check for different allocations. If a
274 # difference is found, call the indicator twice to report both tests.
275 # All runs of each test are counted as one for the statistic.
276 self.indicator.AboutToRun(test)
277 self.remaining -= 1
278 self.failed.append(test)
279 self.indicator.HasRun(test, True)
280 self.indicator.AboutToRun(test)
281 test.output = result[1]
282 self.indicator.HasRun(test, True)
283 elif test.run >= 3:
284 # No difference on the third run -> report a success.
285 self.indicator.AboutToRun(test)
286 self.remaining -= 1
287 self.succeeded += 1
288 test.output = result[1]
289 self.indicator.HasRun(test, False)
290 else:
291 # No difference yet and less than three runs -> add another run and
292 # remember the output for comparison.
293 test.run += 1
294 test.output = result[1]
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000295 pool.add([TestJob(test)])
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000296 # Always update the perf database.
297 return True
298
299 def Run(self, jobs):
300 self.indicator.Starting()
301 self._RunInternal(jobs)
302 self.indicator.Done()
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000303 if self.failed:
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000304 return 1
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000305 elif self.remaining:
306 return 2
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000307 return 0
308
309 def _RunInternal(self, jobs):
310 pool = Pool(jobs)
311 test_map = {}
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000312 queued_exception = [None]
313 def gen_tests():
314 for test in self.tests:
315 assert test.id >= 0
316 test_map[test.id] = test
317 try:
318 yield [TestJob(test)]
319 except Exception, e:
320 # If this failed, save the exception and re-raise it later (after
321 # all other tests have had a chance to run).
322 queued_exception[0] = e
323 continue
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000324 try:
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000325 it = pool.imap_unordered(
326 fn=RunTest,
327 gen=gen_tests(),
328 process_context_fn=MakeProcessContext,
329 process_context_args=[self.context],
330 )
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000331 for result in it:
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000332 if result.heartbeat:
333 self.indicator.Heartbeat()
334 continue
335 test = test_map[result.value[0]]
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000336 if self.context.predictable:
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000337 update_perf = self._ProcessTestPredictable(test, result.value, pool)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000338 else:
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000339 update_perf = self._ProcessTestNormal(test, result.value, pool)
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000340 if update_perf:
341 self._RunPerfSafe(lambda: self.perfdata.UpdatePerfData(test))
342 finally:
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000343 self._VerbosePrint("Closing process pool.")
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000344 pool.terminate()
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000345 self._VerbosePrint("Closing database connection.")
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000346 self._RunPerfSafe(lambda: self.perf_data_manager.close())
347 if self.perf_failures:
348 # Nuke perf data in case of failures. This might not work on windows as
349 # some files might still be open.
350 print "Deleting perf test data due to db corruption."
351 shutil.rmtree(self.datapath)
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000352 if queued_exception[0]:
353 raise queued_exception[0]
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000354
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000355 # Make sure that any allocations were printed in predictable mode (if we
356 # ran any tests).
357 assert (
358 not self.total or
359 not self.context.predictable or
360 self.printed_allocations
361 )
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000362
Ben Murdoch4a90d5f2016-03-22 12:00:34 +0000363 def _VerbosePrint(self, text):
364 if self.context.verbose:
365 print text
366 sys.stdout.flush()
Ben Murdochb8a8cc12014-11-26 15:28:44 +0000367
368
369class BreakNowException(Exception):
370 def __init__(self, value):
371 self.value = value
372 def __str__(self):
373 return repr(self.value)