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

Side by Side Diff: tools/testing/test_runner.py

Issue 9360017: Remove unused support files for old version of tools/test.py. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Remove testcfg.py files. Created 8 years, 10 months 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 | Annotate | Revision Log
« no previous file with comments | « tools/testing/test_configuration.py ('k') | utils/tests/css/testcfg.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
2 # for details. All rights reserved. Use of this source code is governed by a
3 # BSD-style license that can be found in the LICENSE file.
4 #
5 """Classes and methods for executing tasks for the test.py framework.
6
7 This module includes:
8 - Managing parallel execution of tests using threads
9 - Windows and Unix specific code for spawning tasks and retrieving results
10 - Evaluating the output of each test as pass/fail/crash/timeout
11 """
12
13 import ctypes
14 import os
15 import Queue
16 import signal
17 import subprocess
18 import sys
19 import tempfile
20 import threading
21 import time
22 import traceback
23
24 import testing
25 import utils
26
27
28 class Error(Exception):
29 pass
30
31
32 class CommandOutput(object):
33 """Represents the output of running a command."""
34
35 def __init__(self, pid, exit_code, timed_out, stdout, stderr):
36 self.pid = pid
37 self.exit_code = exit_code
38 self.timed_out = timed_out
39 self.stdout = stdout
40 self.stderr = stderr
41 self.failed = None
42
43
44 class TestOutput(object):
45 """Represents the output of running a TestCase."""
46
47 def __init__(self, test, command, output):
48 """Represents the output of running a TestCase.
49
50 Args:
51 test: A TestCase instance.
52 command: the command line that was run
53 output: A CommandOutput instance.
54 """
55 self.test = test
56 self.command = command
57 self.output = output
58
59 def UnexpectedOutput(self):
60 """Compare the result of running the expected from the TestConfiguration.
61
62 Returns:
63 True if the test had an unexpected output.
64 """
65 return not self.GetOutcome() in self.test.outcomes
66
67 def GetOutcome(self):
68 """Returns one of testing.CRASH, testing.TIMEOUT, testing.FAIL, or
69 testing.PASS."""
70 if self.HasCrashed():
71 return testing.CRASH
72 if self.HasTimedOut():
73 return testing.TIMEOUT
74 if self.HasFailed():
75 return testing.FAIL
76 return testing.PASS
77
78 def HasCrashed(self):
79 """Returns True if the test should be considered testing.CRASH."""
80 if utils.IsWindows():
81 if self.output.exit_code == 3:
82 # The VM uses std::abort to terminate on asserts.
83 # std::abort terminates with exit code 3 on Windows.
84 return True
85 return (0x80000000 & self.output.exit_code
86 and not 0x3FFFFF00 & self.output.exit_code)
87 else:
88 # Timed out tests will have exit_code -signal.SIGTERM.
89 if self.output.timed_out:
90 return False
91 if self.output.exit_code == 253:
92 # The Java dartc runners exit 253 in case of unhandled exceptions.
93 return True
94 return self.output.exit_code < 0
95
96 def HasTimedOut(self):
97 """Returns True if the test should be considered as testing.TIMEOUT."""
98 return self.output.timed_out
99
100 def HasFailed(self):
101 """Returns True if the test should be considered as testing.FAIL."""
102 execution_failed = self.test.DidFail(self.output)
103 if self.test.IsNegative():
104 return not execution_failed
105 else:
106 return execution_failed
107
108
109 def Execute(args, context, timeout=None, cwd=None):
110 """Executes the specified command.
111
112 Args:
113 args: sequence of the executable name + arguments.
114 context: An instance of Context object with global settings for test.py.
115 timeout: optional timeout to wait for results in seconds.
116 cwd: optionally change to this working directory.
117
118 Returns:
119 An instance of CommandOutput with the collected results.
120 """
121 (fd_out, outname) = tempfile.mkstemp()
122 (fd_err, errname) = tempfile.mkstemp()
123 (process, exit_code, timed_out) = RunProcess(context, timeout, args=args,
124 stdout=fd_out, stderr=fd_err,
125 cwd=cwd)
126 os.close(fd_out)
127 os.close(fd_err)
128 output = file(outname).read()
129 errors = file(errname).read()
130 utils.CheckedUnlink(outname)
131 utils.CheckedUnlink(errname)
132 result = CommandOutput(process.pid, exit_code, timed_out,
133 output, errors)
134 return result
135
136
137 def KillProcessWithID(pid):
138 """Stop a process (with SIGTERM on Unix)."""
139 if utils.IsWindows():
140 os.popen('taskkill /T /F /PID %d' % pid)
141 else:
142 os.kill(pid, signal.SIGTERM)
143
144
145 MAX_SLEEP_TIME = 0.1
146 INITIAL_SLEEP_TIME = 0.0001
147 SLEEP_TIME_FACTOR = 1.25
148 SEM_INVALID_VALUE = -1
149 SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
150
151
152 def Win32SetErrorMode(mode):
153 """Some weird Windows stuff you just have to do."""
154 prev_error_mode = SEM_INVALID_VALUE
155 try:
156 prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode)
157 except ImportError:
158 pass
159 return prev_error_mode
160
161
162 def RunProcess(context, timeout, args, **rest):
163 """Handles the OS specific details of running a task and saving results."""
164 if context.verbose: print '#', ' '.join(args)
165 popen_args = args
166 prev_error_mode = SEM_INVALID_VALUE
167 if utils.IsWindows():
168 popen_args = '"' + subprocess.list2cmdline(args) + '"'
169 if context.suppress_dialogs:
170 # Try to change the error mode to avoid dialogs on fatal errors. Don't
171 # touch any existing error mode flags by merging the existing error mode.
172 # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx.
173 error_mode = SEM_NOGPFAULTERRORBOX
174 prev_error_mode = Win32SetErrorMode(error_mode)
175 Win32SetErrorMode(error_mode | prev_error_mode)
176 process = subprocess.Popen(shell=utils.IsWindows(),
177 args=popen_args,
178 **rest)
179 if (utils.IsWindows() and context.suppress_dialogs
180 and prev_error_mode != SEM_INVALID_VALUE):
181 Win32SetErrorMode(prev_error_mode)
182 # Compute the end time - if the process crosses this limit we
183 # consider it timed out.
184 if timeout is None: end_time = None
185 else: end_time = time.time() + timeout
186 timed_out = False
187 # Repeatedly check the exit code from the process in a
188 # loop and keep track of whether or not it times out.
189 exit_code = None
190 sleep_time = INITIAL_SLEEP_TIME
191 while exit_code is None:
192 if (not end_time is None) and (time.time() >= end_time):
193 # Kill the process and wait for it to exit.
194 KillProcessWithID(process.pid)
195 # Drain the output pipe from the process to avoid deadlock
196 process.communicate()
197 exit_code = process.wait()
198 timed_out = True
199 else:
200 exit_code = process.poll()
201 time.sleep(sleep_time)
202 sleep_time *= SLEEP_TIME_FACTOR
203 if sleep_time > MAX_SLEEP_TIME:
204 sleep_time = MAX_SLEEP_TIME
205 return (process, exit_code, timed_out)
206
207
208 class TestRunner(object):
209 """Base class for runners."""
210
211 def __init__(self, work_queue, tasks, progress):
212 self.work_queue = work_queue
213 self.tasks = tasks
214 self.terminate = False
215 self.progress = progress
216 self.threads = []
217 self.shutdown_lock = threading.Lock()
218
219
220 class BatchRunner(TestRunner):
221 """Implements communication with a set of subprocesses using threads."""
222
223 def __init__(self, work_queue, tasks, progress, batch_cmd):
224 super(BatchRunner, self).__init__(work_queue, tasks, progress)
225 self.runners = {}
226 self.last_activity = {}
227 self.context = progress.context
228
229 # Scale the number of tasks to the nubmer of CPUs on the machine
230 # 1:1 is too much of an overload on many machines in batch mode,
231 # so scale the ratio of threads to CPUs back. On Windows running
232 # more than one task is not safe.
233 if tasks == testing.USE_DEFAULT_CPUS:
234 if utils.IsWindows():
235 tasks = 1
236 else:
237 tasks = .75 * testing.HOST_CPUS
238
239 # Start threads
240 for i in xrange(tasks):
241 thread = threading.Thread(target=self.RunThread, args=[batch_cmd, i])
242 self.threads.append(thread)
243 thread.daemon = True
244 thread.start()
245
246 def RunThread(self, batch_cmd, thread_number):
247 """A thread started to feed a single TestRunner."""
248 try:
249 runner = None
250 while not self.terminate and not self.work_queue.empty():
251 runner = subprocess.Popen(batch_cmd,
252 stdin=subprocess.PIPE,
253 stderr=subprocess.STDOUT,
254 stdout=subprocess.PIPE)
255 self.runners[thread_number] = runner
256 self.FeedTestRunner(runner, thread_number)
257 if thread_number in self.last_activity:
258 del self.last_activity[thread_number]
259
260 # Cleanup
261 self.EndRunner(runner)
262
263 except:
264 self.Shutdown()
265 raise
266 finally:
267 if thread_number in self.last_activity:
268 del self.last_activity[thread_number]
269 if runner: self.EndRunner(runner)
270
271 def EndRunner(self, runner):
272 """Cleans up a single runner, killing the child if necessary."""
273 with self.shutdown_lock:
274 if runner:
275 returncode = runner.poll()
276 if returncode is None:
277 runner.kill()
278 for (found_runner, thread_number) in self.runners.items():
279 if runner == found_runner:
280 del self.runners[thread_number]
281 break
282 try:
283 runner.communicate()
284 except ValueError:
285 pass
286
287 def CheckForTimeouts(self):
288 now = time.time()
289 for (thread_number, start_time) in self.last_activity.items():
290 if now - start_time > self.context.timeout:
291 self.runners[thread_number].kill()
292
293 def WaitForCompletion(self):
294 """Wait for threads to finish, and monitor test runners for timeouts."""
295 for t in self.threads:
296 while True:
297 self.CheckForTimeouts()
298 t.join(timeout=5)
299 if not t.isAlive():
300 break
301
302 def FeedTestRunner(self, runner, thread_number):
303 """Feed commands to the fork'ed TestRunner through a Popen object."""
304
305 last_case = {}
306 last_buf = ''
307
308 while not self.terminate:
309 # Is the runner still alive?
310 returninfo = runner.poll()
311 if returninfo is not None:
312 buf = last_buf + '\n' + runner.stdout.read()
313 if last_case:
314 self.RecordPassFail(last_case, buf, testing.CRASH)
315 else:
316 with self.progress.lock:
317 print >>sys. stderr, ('%s: runner unexpectedly exited: %d'
318 % (threading.currentThread().name,
319 returninfo))
320 print 'Crash Output: '
321 print
322 print buf
323 return
324
325 try:
326 case = self.work_queue.get_nowait()
327 with self.progress.lock:
328 self.progress.AboutToRun(case.case)
329
330 except Queue.Empty:
331 return
332 test_case = case.case
333 cmd = ' '.join(test_case.GetCommand()[1:])
334
335 try:
336 print >>runner.stdin, cmd
337 except IOError:
338 with self.progress.lock:
339 traceback.print_exc()
340
341 # Child exited before starting the next command.
342 buf = last_buf + '\n' + runner.stdout.read()
343 self.RecordPassFail(last_case, buf, testing.CRASH)
344
345 # We never got a chance to run this command - queue it back up.
346 self.work_queue.put(case)
347 return
348
349 buf = ''
350 self.last_activity[thread_number] = time.time()
351 while not self.terminate:
352 line = runner.stdout.readline()
353 if self.terminate:
354 break
355 case.case.duration = time.time() - self.last_activity[thread_number]
356 if not line:
357 # EOF. Child has exited.
358 if case.case.duration > self.context.timeout:
359 with self.progress.lock:
360 print 'Child timed out after %d seconds' % self.context.timeout
361 self.RecordPassFail(case, buf, testing.TIMEOUT)
362 elif buf:
363 self.RecordPassFail(case, buf, testing.CRASH)
364 return
365
366 # Look for TestRunner batch status escape sequence. e.g.
367 # >>> TEST PASS
368 if line.startswith('>>> '):
369 result = line.split()
370 if result[1] == 'TEST':
371 outcome = result[2].lower()
372
373 # Read the rest of the output buffer (possible crash output)
374 if outcome == testing.CRASH:
375 buf += runner.stdout.read()
376
377 self.RecordPassFail(case, buf, outcome)
378
379 # Always handle crashes by restarting the runner.
380 if outcome == testing.CRASH:
381 return
382 break
383 elif result[1] == 'BATCH':
384 pass
385 else:
386 print 'Unknown cmd from batch runner: %s' % line
387 else:
388 buf += line
389
390 # If the process crashes before the next command is executed,
391 # save info to report diagnostics.
392 last_buf = buf
393 last_case = case
394
395 def RecordPassFail(self, case, stdout_buf, outcome):
396 """An unexpected failure occurred."""
397 if outcome == testing.PASS or outcome == testing.OKAY:
398 exit_code = 0
399 elif outcome == testing.CRASH:
400 exit_code = -1
401 elif outcome == testing.FAIL or outcome == testing.TIMEOUT:
402 exit_code = 1
403 else:
404 assert False, 'Unexpected outcome: %s' % outcome
405
406 cmd_output = CommandOutput(0, exit_code,
407 outcome == testing.TIMEOUT, stdout_buf, '')
408 test_output = TestOutput(case.case,
409 case.case.GetCommand(),
410 cmd_output)
411 with self.progress.lock:
412 if test_output.UnexpectedOutput():
413 self.progress.failed.append(test_output)
414 else:
415 self.progress.succeeded += 1
416 if outcome == testing.CRASH:
417 self.progress.crashed += 1
418 self.progress.remaining -= 1
419 self.progress.HasRun(test_output)
420
421 def Shutdown(self):
422 """Kill all active runners."""
423 print 'Shutting down remaining runners.'
424 self.terminate = True
425 for runner in self.runners.values():
426 runner.kill()
427 # Give threads a chance to exit gracefully
428 time.sleep(2)
429 for runner in self.runners.values():
430 self.EndRunner(runner)
OLDNEW
« no previous file with comments | « tools/testing/test_configuration.py ('k') | utils/tests/css/testcfg.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698