Chromium Code Reviews| Index: tools/testing/test_runner.py |
| diff --git a/tools/testing/test_runner.py b/tools/testing/test_runner.py |
| index c3cde9f85eec47759b3dc2b8e581ab843d5ddf1d..8cf4f52f4e8782388dcf71d1257903711fa531b8 100755 |
| --- a/tools/testing/test_runner.py |
| +++ b/tools/testing/test_runner.py |
| @@ -1,18 +1,24 @@ |
| -#!/usr/bin/env python |
| -# |
| # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| # for details. All rights reserved. Use of this source code is governed by a |
| # BSD-style license that can be found in the LICENSE file. |
| # |
| +"""Classes and methods for executing tasks for the test.py framework. |
| + |
| +This module includes: |
| + - Windows and Unix specific code for spawning tasks and retrieving results |
| + - Managing parallel execution for threads |
| +""" |
| +import ctypes |
| import os |
| +import Queue |
| +import signal |
| import subprocess |
| import sys |
| import tempfile |
| -import time |
| import threading |
| +import time |
| import traceback |
| -import Queue |
| import testing |
| import utils |
| @@ -22,14 +28,8 @@ class Error(Exception): |
| pass |
| -def _CheckedUnlink(name): |
| - try: |
| - os.unlink(name) |
| - except OSError, e: |
| - PrintError("os.unlink() " + str(e)) |
| - |
| - |
| class CommandOutput(object): |
| + """Represents the output of running a command.""" |
| def __init__(self, pid, exit_code, timed_out, stdout, stderr): |
| self.pid = pid |
| @@ -41,13 +41,26 @@ class CommandOutput(object): |
| class TestOutput(object): |
| + """Represents the output of running a TestCase.""" |
| def __init__(self, test, command, output): |
| + """Represents the output of running a TestCase. |
| + |
| + Args: |
| + test: A TestCase instance. |
| + command: the command line that was run |
| + output: A CommandOutput instance. |
| + """ |
| self.test = test |
| self.command = command |
| self.output = output |
| def UnexpectedOutput(self): |
| + """Compare the result of running the expected from the TestConfiguration. |
| + |
| + Returns: |
| + True if the test had an unexpected output. |
| + """ |
| if self.HasCrashed(): |
| outcome = testing.CRASH |
| elif self.HasTimedOut(): |
| @@ -59,12 +72,14 @@ class TestOutput(object): |
| return not outcome in self.test.outcomes |
| def HasCrashed(self): |
| + """Returns True if the test should be considered testing.CRASH.""" |
| if utils.IsWindows(): |
| if self.output.exit_code == 3: |
| # The VM uses std::abort to terminate on asserts. |
| # std::abort terminates with exit code 3 on Windows. |
| return True |
| - return 0x80000000 & self.output.exit_code and not (0x3FFFFF00 & self.output.exit_code) |
| + return (0x80000000 & self.output.exit_code |
| + and not 0x3FFFFF00 & self.output.exit_code) |
| else: |
| # Timed out tests will have exit_code -signal.SIGTERM. |
| if self.output.timed_out: |
| @@ -75,9 +90,11 @@ class TestOutput(object): |
| return self.output.exit_code < 0 |
| def HasTimedOut(self): |
| - return self.output.timed_out; |
| + """Returns True if the test should be considered as testing.TIMEOUT.""" |
| + return self.output.timed_out |
| def HasFailed(self): |
| + """Returns True if the test should be considered as testing.FAIL.""" |
| execution_failed = self.test.DidFail(self.output) |
| if self.test.IsNegative(): |
| return not execution_failed |
| @@ -86,28 +103,35 @@ class TestOutput(object): |
| def Execute(args, context, timeout=None, cwd=None): |
| + """Executes the specified command. |
| + |
| + Args: |
| + args: sequence of the executable name + arguments. |
| + context: An instance of Context object with global settings for test.py. |
| + timeout: optional timeout to wait for results in seconds. |
| + cwd: optionally change to this working directory. |
| + |
| + Returns: |
| + An instance of CommandOutput with the collected results. |
| + """ |
| (fd_out, outname) = tempfile.mkstemp() |
| (fd_err, errname) = tempfile.mkstemp() |
| - (process, exit_code, timed_out) = RunProcess( |
| - context, |
| - timeout, |
| - args = args, |
| - stdout = fd_out, |
| - stderr = fd_err, |
| - cwd = cwd |
| - ) |
| + (process, exit_code, timed_out) = RunProcess(context, timeout, args=args, |
| + stdout=fd_out, stderr=fd_err, |
| + cwd=cwd) |
| os.close(fd_out) |
| os.close(fd_err) |
| output = file(outname).read() |
| errors = file(errname).read() |
| - _CheckedUnlink(outname) |
| - _CheckedUnlink(errname) |
| + utils.CheckedUnlink(outname) |
| + utils.CheckedUnlink(errname) |
| result = CommandOutput(process.pid, exit_code, timed_out, |
| output, errors) |
| return result |
| def KillProcessWithID(pid): |
| + """Stop a process (with SIGTERM on Unix).""" |
| if utils.IsWindows(): |
| os.popen('taskkill /T /F /PID %d' % pid) |
| else: |
| @@ -117,39 +141,39 @@ def KillProcessWithID(pid): |
| MAX_SLEEP_TIME = 0.1 |
| INITIAL_SLEEP_TIME = 0.0001 |
| SLEEP_TIME_FACTOR = 1.25 |
| - |
| SEM_INVALID_VALUE = -1 |
| -SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h |
| +SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h |
| def Win32SetErrorMode(mode): |
| + """Some weird Windows stuff you just have to do.""" |
|
ahe
2011/10/12 12:14:11
:-)
zundel
2011/10/12 14:13:49
I see you didn't sleep through this review!
|
| prev_error_mode = SEM_INVALID_VALUE |
| try: |
| - import ctypes |
| - prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode); |
| + prev_error_mode = ctypes.windll.kernel32.SetErrorMode(mode) |
| except ImportError: |
| pass |
| return prev_error_mode |
| + |
| def RunProcess(context, timeout, args, **rest): |
| - if context.verbose: print "#", " ".join(args) |
| + """Handles the OS specific details of running a task and saving results.""" |
| + if context.verbose: print '#', ' '.join(args) |
| popen_args = args |
| - prev_error_mode = SEM_INVALID_VALUE; |
| + prev_error_mode = SEM_INVALID_VALUE |
| if utils.IsWindows(): |
| popen_args = '"' + subprocess.list2cmdline(args) + '"' |
| if context.suppress_dialogs: |
| # Try to change the error mode to avoid dialogs on fatal errors. Don't |
| # touch any existing error mode flags by merging the existing error mode. |
| # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx. |
| - error_mode = SEM_NOGPFAULTERRORBOX; |
| - prev_error_mode = Win32SetErrorMode(error_mode); |
| - Win32SetErrorMode(error_mode | prev_error_mode); |
| - process = subprocess.Popen( |
| - shell = utils.IsWindows(), |
| - args = popen_args, |
| - **rest |
| - ) |
| - if utils.IsWindows() and context.suppress_dialogs and prev_error_mode != SEM_INVALID_VALUE: |
| + error_mode = SEM_NOGPFAULTERRORBOX |
| + prev_error_mode = Win32SetErrorMode(error_mode) |
| + Win32SetErrorMode(error_mode | prev_error_mode) |
| + process = subprocess.Popen(shell=utils.IsWindows(), |
| + args=popen_args, |
| + **rest) |
| + if (utils.IsWindows() and context.suppress_dialogs |
| + and prev_error_mode != SEM_INVALID_VALUE): |
| Win32SetErrorMode(prev_error_mode) |
| # Compute the end time - if the process crosses this limit we |
| # consider it timed out. |
| @@ -171,14 +195,15 @@ def RunProcess(context, timeout, args, **rest): |
| else: |
| exit_code = process.poll() |
| time.sleep(sleep_time) |
| - sleep_time = sleep_time * SLEEP_TIME_FACTOR |
| + sleep_time *= SLEEP_TIME_FACTOR |
| if sleep_time > MAX_SLEEP_TIME: |
| sleep_time = MAX_SLEEP_TIME |
| return (process, exit_code, timed_out) |
| class TestRunner(object): |
| - """Base class for runners """ |
| + """Base class for runners.""" |
| + |
| def __init__(self, work_queue, tasks, progress): |
| self.work_queue = work_queue |
| self.tasks = tasks |
| @@ -197,7 +222,6 @@ class BatchRunner(TestRunner): |
| self.last_activity = {} |
| self.context = progress.context |
| - |
| # Scale the number of tasks to the nubmer of CPUs on the machine |
| # 1:1 is too much of an overload on many machines in batch mode, |
| # so scale the ratio of threads to CPUs back. |
| @@ -222,33 +246,33 @@ class BatchRunner(TestRunner): |
| stdout=subprocess.PIPE) |
| self.runners[thread_number] = runner |
| self.FeedTestRunner(runner, thread_number) |
| - if self.last_activity.has_key(thread_number): |
| + if thread_number in self.last_activity: |
| del self.last_activity[thread_number] |
| - # cleanup |
| + # Cleanup |
| self.EndRunner(runner) |
| except: |
| self.Shutdown() |
| raise |
| finally: |
| - if self.last_activity.has_key(thread_number): |
| + if thread_number in self.last_activity: |
| del self.last_activity[thread_number] |
| if runner: self.EndRunner(runner) |
| def EndRunner(self, runner): |
| - """ Cleans up a single runner, killing the child if necessary""" |
| + """Cleans up a single runner, killing the child if necessary.""" |
| with self.shutdown_lock: |
| if runner: |
| returncode = runner.poll() |
| - if returncode == None: |
| + if returncode is None: |
| runner.kill() |
| for (found_runner, thread_number) in self.runners.items(): |
| if runner == found_runner: |
| del self.runners[thread_number] |
| break |
| try: |
| - runner.communicate(); |
| + runner.communicate() |
| except ValueError: |
| pass |
| @@ -259,7 +283,7 @@ class BatchRunner(TestRunner): |
| self.runners[thread_number].kill() |
| def WaitForCompletion(self): |
| - """ Wait for threads to finish, and monitor test runners for timeouts.""" |
| + """Wait for threads to finish, and monitor test runners for timeouts.""" |
| for t in self.threads: |
| while True: |
| self.CheckForTimeouts() |
| @@ -282,8 +306,9 @@ class BatchRunner(TestRunner): |
| self.RecordPassFail(last_case, buf, testing.CRASH) |
| else: |
| with self.progress.lock: |
| - print >>sys. stderr, ("%s: runner unexpectedly exited: %d" |
| - % (threading.currentThread().name, returninfo)) |
| + print >>sys. stderr, ('%s: runner unexpectedly exited: %d' |
| + % (threading.currentThread().name, |
| + returninfo)) |
| print 'Crash Output: ' |
| print buf |
| @@ -297,7 +322,7 @@ class BatchRunner(TestRunner): |
| except Queue.Empty: |
| return |
| test_case = case.case |
| - cmd = " ".join(test_case.GetCommand()[1:]) |
| + cmd = ' '.join(test_case.GetCommand()[1:]) |
| try: |
| print >>runner.stdin, cmd |
| @@ -313,22 +338,22 @@ class BatchRunner(TestRunner): |
| self.work_queue.put(case) |
| return |
| - buf = "" |
| + buf = '' |
| self.last_activity[thread_number] = time.time() |
| while not self.terminate: |
| line = runner.stdout.readline() |
| if self.terminate: |
| - break; |
| - case.case.duration = time.time() - self.last_activity[thread_number]; |
| + break |
| + case.case.duration = time.time() - self.last_activity[thread_number] |
| if not line: |
| - # EOF. Child has exited. |
| - if case.case.duration > self.context.timeout: |
| - with self.progress.lock: |
| - print "Child timed out after %d seconds" % self.context.timeout |
| - self.RecordPassFail(case, buf, testing.TIMEOUT) |
| - elif buf: |
| - self.RecordPassFail(case, buf, testing.CRASH) |
| - return |
| + # EOF. Child has exited. |
| + if case.case.duration > self.context.timeout: |
| + with self.progress.lock: |
| + print 'Child timed out after %d seconds' % self.context.timeout |
| + self.RecordPassFail(case, buf, testing.TIMEOUT) |
| + elif buf: |
| + self.RecordPassFail(case, buf, testing.CRASH) |
| + return |
| # Look for TestRunner batch status escape sequence. e.g. |
| # >>> TEST PASS |
| @@ -368,13 +393,13 @@ class BatchRunner(TestRunner): |
| elif outcome == testing.FAIL or outcome == testing.TIMEOUT: |
| exit_code = 1 |
| else: |
| - assert false, "Unexpected outcome: %s" % outcome |
| + assert False, 'Unexpected outcome: %s' % outcome |
| cmd_output = CommandOutput(0, exit_code, |
| - outcome == testing.TIMEOUT, stdout_buf, "") |
| + outcome == testing.TIMEOUT, stdout_buf, '') |
| test_output = TestOutput(case.case, |
| - case.case.GetCommand(), |
| - cmd_output) |
| + case.case.GetCommand(), |
| + cmd_output) |
| with self.progress.lock: |
| if test_output.UnexpectedOutput(): |
| self.progress.failed.append(test_output) |
| @@ -386,9 +411,9 @@ class BatchRunner(TestRunner): |
| self.progress.HasRun(test_output) |
| def Shutdown(self): |
| - """Kill all active runners""" |
| - print "Shutting down remaining runners" |
| - self.terminate = True; |
| + """Kill all active runners.""" |
| + print 'Shutting down remaining runners.' |
| + self.terminate = True |
| for runner in self.runners.values(): |
| runner.kill() |
| # Give threads a chance to exit gracefully |