Commit 3b065110 authored by Michal Majewski's avatar Michal Majewski Committed by Commit Bot

Reland "[test] Creating command before execution phase."

This is a reland of 98cc9e86
Original change's description:
> [test] Creating command before execution phase.
> 
> Immutable command class with shell, flags and
> environment.
> 
> Command creation moved from worker to the main
> process. Because of that there is no need to send
> test cases beyond process boundaries and load test
> suites in worker processes.
> 
> Bug: v8:6917
> Change-Id: Ib6a44278095b4f7141eb9b96802fe3e8117678a6
> Reviewed-on: https://chromium-review.googlesource.com/791710
> Commit-Queue: Michał Majewski <majeski@google.com>
> Reviewed-by: Michael Achenbach <machenbach@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#49746}

Bug: v8:6917
Change-Id: I49c29a8db813c47909f2cc45070ac7721a447c7a
Reviewed-on: https://chromium-review.googlesource.com/800370Reviewed-by: 's avatarMichael Achenbach <machenbach@chromium.org>
Commit-Queue: Michał Majewski <majeski@google.com>
Cr-Commit-Position: refs/heads/master@{#49756}
parent bbd9e4a7
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
import os import os
import shutil import shutil
from testrunner.local import commands from testrunner.local import command
from testrunner.local import testsuite from testrunner.local import testsuite
from testrunner.local import utils from testrunner.local import utils
from testrunner.objects import testcase from testrunner.objects import testcase
...@@ -37,21 +37,17 @@ from testrunner.objects import testcase ...@@ -37,21 +37,17 @@ from testrunner.objects import testcase
class CcTestSuite(testsuite.TestSuite): class CcTestSuite(testsuite.TestSuite):
SHELL = 'cctest' SHELL = 'cctest'
def __init__(self, name, root):
super(CcTestSuite, self).__init__(name, root)
if utils.IsWindows():
build_dir = "build"
else:
build_dir = "out"
def ListTests(self, context): def ListTests(self, context):
shell = os.path.abspath(os.path.join(context.shell_dir, self.SHELL)) shell = os.path.abspath(os.path.join(context.shell_dir, self.SHELL))
if utils.IsWindows(): if utils.IsWindows():
shell += ".exe" shell += ".exe"
cmd = context.command_prefix + [shell, "--list"] + context.extra_flags cmd = command.Command(
output = commands.Execute(cmd) cmd_prefix=context.command_prefix,
shell=shell,
args=["--list"] + context.extra_flags)
output = cmd.execute()
if output.exit_code != 0: if output.exit_code != 0:
print ' '.join(cmd) print cmd
print output.stdout print output.stdout
print output.stderr print output.stderr
return [] return []
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
'../tools/run_perf.py', '../tools/run_perf.py',
# TODO(machenbach): These files are referenced by the perf runner. # TODO(machenbach): These files are referenced by the perf runner.
# They should be transformed into a proper python module. # They should be transformed into a proper python module.
'../tools/testrunner/local/commands.py', '../tools/testrunner/local/command.py',
'../tools/testrunner/local/utils.py', '../tools/testrunner/local/utils.py',
'../tools/testrunner/objects/output.py', '../tools/testrunner/objects/output.py',
# This is often used to trigger performance bots. We include it in the # This is often used to trigger performance bots. We include it in the
......
...@@ -12,7 +12,6 @@ Examples: ...@@ -12,7 +12,6 @@ Examples:
''' '''
from collections import OrderedDict from collections import OrderedDict
import commands
import json import json
import math import math
from argparse import ArgumentParser from argparse import ArgumentParser
......
...@@ -12,7 +12,6 @@ from standard input or via the --filename option. Examples: ...@@ -12,7 +12,6 @@ from standard input or via the --filename option. Examples:
%prog -f results.json -t "ia32 results" -o results.html %prog -f results.json -t "ia32 results" -o results.html
''' '''
import commands
import json import json
import math import math
from optparse import OptionParser from optparse import OptionParser
......
...@@ -106,7 +106,7 @@ import re ...@@ -106,7 +106,7 @@ import re
import subprocess import subprocess
import sys import sys
from testrunner.local import commands from testrunner.local import command
from testrunner.local import utils from testrunner.local import utils
ARCH_GUESS = utils.DefaultArch() ARCH_GUESS = utils.DefaultArch()
...@@ -493,15 +493,23 @@ class RunnableConfig(GraphConfig): ...@@ -493,15 +493,23 @@ class RunnableConfig(GraphConfig):
suffix = ["--"] + self.test_flags if self.test_flags else [] suffix = ["--"] + self.test_flags if self.test_flags else []
return self.flags + (extra_flags or []) + [self.main] + suffix return self.flags + (extra_flags or []) + [self.main] + suffix
def GetCommand(self, shell_dir, extra_flags=None): def GetCommand(self, cmd_prefix, shell_dir, extra_flags=None):
# TODO(machenbach): This requires +.exe if run on windows. # TODO(machenbach): This requires +.exe if run on windows.
extra_flags = extra_flags or [] extra_flags = extra_flags or []
cmd = [os.path.join(shell_dir, self.binary)]
if self.binary.endswith(".py"):
cmd = [sys.executable] + cmd
if self.binary != 'd8' and '--prof' in extra_flags: if self.binary != 'd8' and '--prof' in extra_flags:
print "Profiler supported only on a benchmark run with d8" print "Profiler supported only on a benchmark run with d8"
return cmd + self.GetCommandFlags(extra_flags=extra_flags)
if self.process_size:
cmd_prefix = ["/usr/bin/time", "--format=MaxMemory: %MKB"] + cmd_prefix
if self.binary.endswith('.py'):
# Copy cmd_prefix instead of update (+=).
cmd_prefix = cmd_prefix + [sys.executable]
return command.Command(
cmd_prefix=cmd_prefix,
shell=os.path.join(shell_dir, self.binary),
args=self.GetCommandFlags(extra_flags=extra_flags),
timeout=self.timeout or 60)
def Run(self, runner, trybot): def Run(self, runner, trybot):
"""Iterates over several runs and handles the output for all traces.""" """Iterates over several runs and handles the output for all traces."""
...@@ -677,18 +685,9 @@ class DesktopPlatform(Platform): ...@@ -677,18 +685,9 @@ class DesktopPlatform(Platform):
suffix = ' - secondary' if secondary else '' suffix = ' - secondary' if secondary else ''
shell_dir = self.shell_dir_secondary if secondary else self.shell_dir shell_dir = self.shell_dir_secondary if secondary else self.shell_dir
title = ">>> %%s (#%d)%s:" % ((count + 1), suffix) title = ">>> %%s (#%d)%s:" % ((count + 1), suffix)
if runnable.process_size: cmd = runnable.GetCommand(self.command_prefix, shell_dir, self.extra_flags)
command = ["/usr/bin/time", "--format=MaxMemory: %MKB"]
else:
command = []
command += self.command_prefix + runnable.GetCommand(shell_dir,
self.extra_flags)
try: try:
output = commands.Execute( output = cmd.execute()
command,
timeout=runnable.timeout,
)
except OSError as e: # pragma: no cover except OSError as e: # pragma: no cover
print title % "OSError" print title % "OSError"
print e print e
......
# Copyright 2017 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from contextlib import contextmanager
import os
import subprocess
import sys
import threading
from ..local import utils
from ..objects import output
@contextmanager
def win_error_mode():
""" 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.
"""
def set_error_mode(mode):
prev_error_mode = SEM_INVALID_VALUE
try:
import ctypes
prev_error_mode = (
ctypes.windll.kernel32.SetErrorMode(mode)) #@UndefinedVariable
except ImportError:
pass
return prev_error_mode
SEM_INVALID_VALUE = -1
SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
if utils.IsWindows():
error_mode = SEM_NOGPFAULTERRORBOX
prev_error_mode = set_error_mode(error_mode)
set_error_mode(error_mode | prev_error_mode)
yield
if utils.IsWindows() and prev_error_mode != SEM_INVALID_VALUE:
set_error_mode(prev_error_mode)
class Command(object):
def __init__(self, shell, args=None, cmd_prefix=None, timeout=60, env=None,
verbose=False):
assert(timeout > 0)
self.shell = shell
self.args = args or []
self.cmd_prefix = cmd_prefix or []
self.timeout = timeout
self.env = env or {}
self.verbose = verbose
def execute(self, **additional_popen_kwargs):
if self.verbose:
print '# %s' % self
with win_error_mode():
try:
process = subprocess.Popen(
args=self._get_popen_args(),
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self._get_env(),
**additional_popen_kwargs
)
except Exception as e:
sys.stderr.write('Error executing: %s\n' % self)
raise e
# Variable to communicate with the timer.
timeout_occured = [False]
timer = threading.Timer(
self.timeout, self._kill_process, [process, timeout_occured])
timer.start()
stdout, stderr = process.communicate()
timer.cancel()
return output.Output(
process.returncode,
timeout_occured[0],
stdout.decode('utf-8', 'replace').encode('utf-8'),
stderr.decode('utf-8', 'replace').encode('utf-8'),
process.pid,
)
def _get_popen_args(self):
args = self._to_args_list()
if utils.IsWindows():
return subprocess.list2cmdline(args)
return args
def _get_env(self):
env = os.environ.copy()
env.update(self.env)
# GTest shard information is read by the V8 tests runner. Make sure it
# doesn't leak into the execution of gtests we're wrapping. Those might
# otherwise apply a second level of sharding and as a result skip tests.
env.pop('GTEST_TOTAL_SHARDS', None)
env.pop('GTEST_SHARD_INDEX', None)
return env
def _kill_process(self, process, timeout_occured):
timeout_occured[0] = True
try:
if utils.IsWindows():
self._kill_process_windows(process)
else:
self._kill_process_posix(process)
except OSError:
sys.stderr.write('Error: Process %s already ended.\n' % process.pid)
def _kill_process_windows(self, process):
if self.verbose:
print 'Attempting to kill process %d' % process.pid
sys.stdout.flush()
tk = subprocess.Popen(
'taskkill /T /F /PID %d' % process.pid,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = tk.communicate()
if self.verbose:
print 'Taskkill results for %d' % process.pid
print stdout
print stderr
print 'Return code: %d' % tk.returncode
sys.stdout.flush()
def _kill_process_posix(self, process):
if utils.GuessOS() == 'macos':
# TODO(machenbach): Temporary output for investigating hanging test
# driver on mac.
print 'Attempting to kill process %d - cmd %s' % (process.pid, self)
try:
print subprocess.check_output(
'ps -e | egrep "d8|cctest|unittests"', shell=True)
except Exception:
pass
sys.stdout.flush()
process.kill()
if utils.GuessOS() == 'macos':
# TODO(machenbach): Temporary output for investigating hanging test
# driver on mac. This will probably not print much, since kill only
# sends the signal.
print 'Return code after signalling the kill: %s' % process.returncode
sys.stdout.flush()
def __str__(self):
return self.to_string()
def to_string(self, relative=False):
def escape(part):
# Escape spaces. We may need to escape more characters for this to work
# properly.
if ' ' in part:
return '"%s"' % part
return part
parts = map(escape, self._to_args_list())
cmd = ' '.join(parts)
if relative:
cmd = cmd.replace(os.getcwd() + os.sep, '')
return cmd
def _to_args_list(self):
return self.cmd_prefix + [self.shell] + self.args
# Copyright 2012 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import subprocess
import sys
from threading import Timer
from ..local import utils
from ..objects import output
SEM_INVALID_VALUE = -1
SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h
def Win32SetErrorMode(mode):
prev_error_mode = SEM_INVALID_VALUE
try:
import ctypes
prev_error_mode = \
ctypes.windll.kernel32.SetErrorMode(mode) #@UndefinedVariable
except ImportError:
pass
return prev_error_mode
def RunProcess(verbose, timeout, args, additional_env, **rest):
if verbose: print "#", " ".join(args)
popen_args = args
prev_error_mode = SEM_INVALID_VALUE
if utils.IsWindows():
popen_args = subprocess.list2cmdline(args)
# 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)
env = os.environ.copy()
env.update(additional_env)
# GTest shard information is read by the V8 tests runner. Make sure it
# doesn't leak into the execution of gtests we're wrapping. Those might
# otherwise apply a second level of sharding and as a result skip tests.
env.pop('GTEST_TOTAL_SHARDS', None)
env.pop('GTEST_SHARD_INDEX', None)
try:
process = subprocess.Popen(
args=popen_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=env,
**rest
)
except Exception as e:
sys.stderr.write("Error executing: %s\n" % popen_args)
raise e
if (utils.IsWindows() and prev_error_mode != SEM_INVALID_VALUE):
Win32SetErrorMode(prev_error_mode)
def kill_process(process, timeout_result):
timeout_result[0] = True
try:
if utils.IsWindows():
if verbose:
print "Attempting to kill process %d" % process.pid
sys.stdout.flush()
tk = subprocess.Popen(
'taskkill /T /F /PID %d' % process.pid,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
stdout, stderr = tk.communicate()
if verbose:
print "Taskkill results for %d" % process.pid
print stdout
print stderr
print "Return code: %d" % tk.returncode
sys.stdout.flush()
else:
if utils.GuessOS() == "macos":
# TODO(machenbach): Temporary output for investigating hanging test
# driver on mac.
print "Attempting to kill process %d - cmd %s" % (process.pid, args)
try:
print subprocess.check_output(
"ps -e | egrep 'd8|cctest|unittests'", shell=True)
except Exception:
pass
sys.stdout.flush()
process.kill()
if utils.GuessOS() == "macos":
# TODO(machenbach): Temporary output for investigating hanging test
# driver on mac. This will probably not print much, since kill only
# sends the signal.
print "Return code after signalling the kill: %s" % process.returncode
sys.stdout.flush()
except OSError:
sys.stderr.write('Error: Process %s already ended.\n' % process.pid)
# Pseudo object to communicate with timer thread.
timeout_result = [False]
timer = Timer(timeout, kill_process, [process, timeout_result])
timer.start()
stdout, stderr = process.communicate()
timer.cancel()
return output.Output(
process.returncode,
timeout_result[0],
stdout.decode('utf-8', 'replace').encode('utf-8'),
stderr.decode('utf-8', 'replace').encode('utf-8'),
process.pid,
)
# TODO(machenbach): Instead of passing args around, we should introduce an
# immutable Command class (that just represents the command with all flags and
# is pretty-printable) and a member method for running such a command.
def Execute(args, verbose=False, timeout=None, env=None):
args = [ c for c in args if c != "" ]
return RunProcess(verbose, timeout, args, env or {})
...@@ -34,10 +34,9 @@ import sys ...@@ -34,10 +34,9 @@ import sys
import time import time
from pool import Pool from pool import Pool
from . import commands from . import command
from . import perfdata from . import perfdata
from . import statusfile from . import statusfile
from . import testsuite
from . import utils from . import utils
from ..objects import output from ..objects import output
...@@ -48,76 +47,18 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname( ...@@ -48,76 +47,18 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(
TEST_DIR = os.path.join(BASE_DIR, "test") TEST_DIR = os.path.join(BASE_DIR, "test")
class Instructions(object):
def __init__(self, command, test_id, timeout, verbose, env):
self.command = command
self.id = test_id
self.timeout = timeout
self.verbose = verbose
self.env = env
# Structure that keeps global information per worker process. # Structure that keeps global information per worker process.
ProcessContext = collections.namedtuple( ProcessContext = collections.namedtuple(
"process_context", ["suites", "context"]) 'process_context', ['sancov_dir'])
def MakeProcessContext(context, suite_names): def MakeProcessContext(sancov_dir):
"""Generate a process-local context. return ProcessContext(sancov_dir)
This reloads all suites per process and stores the global context. # Global function for multiprocessing, because pickling a static method doesn't
# work on Windows.
Args: def run_job(job, process_context):
context: The global context from the test runner. return job.run(process_context)
suite_names (list of str): Suite names as loaded by the parent process.
Load the same suites in each subprocess.
"""
suites = {}
for root in suite_names:
# Don't reinitialize global state as this is concurrently called from
# different processes.
suite = testsuite.TestSuite.LoadTestSuite(
os.path.join(TEST_DIR, root), global_init=False)
if suite:
suites[suite.name] = suite
return ProcessContext(suites, context)
def GetCommand(test, context):
d8testflag = []
shell = test.suite.GetShellForTestCase(test)
if shell == "d8":
d8testflag = ["--test"]
if utils.IsWindows():
shell += ".exe"
if context.random_seed:
d8testflag += ["--random-seed=%s" % context.random_seed]
files, flags, env = test.suite.GetParametersForTestCase(test, context)
cmd = (
context.command_prefix +
[os.path.abspath(os.path.join(context.shell_dir, shell))] +
d8testflag +
files +
context.extra_flags +
# Flags from test cases can overwrite extra cmd-line flags.
flags
)
return cmd, env
def _GetInstructions(test, context):
command, env = GetCommand(test, context)
timeout = context.timeout
if ("--stress-opt" in test.flags or
"--stress-opt" in context.mode_flags or
"--stress-opt" in context.extra_flags):
timeout *= 4
if "--noenable-vfp3" in context.extra_flags:
timeout *= 2
# TODO(majeski): make it slow outcome dependent.
timeout *= 2
return Instructions(command, test.id, timeout, context.verbose, env)
class Job(object): class Job(object):
...@@ -126,31 +67,17 @@ class Job(object): ...@@ -126,31 +67,17 @@ class Job(object):
All contained fields will be pickled/unpickled. All contained fields will be pickled/unpickled.
""" """
def Run(self, process_context): def run(self, process_context):
"""Executes the job.
Args:
process_context: Process-local information that is initialized by the
executing worker.
"""
raise NotImplementedError() raise NotImplementedError()
def SetupProblem(exception, test):
stderr = ">>> EXCEPTION: %s\n" % exception
match = re.match(r"^.*No such file or directory: '(.*)'$", str(exception))
if match:
# Extra debuging information when files are claimed missing.
f = match.group(1)
stderr += ">>> File %s exists? -> %s\n" % (f, os.path.exists(f))
return test.id, output.Output(1, False, "", stderr, None), 0
class TestJob(Job): class TestJob(Job):
def __init__(self, test): def __init__(self, test_id, cmd, run_num):
self.test = test self.test_id = test_id
self.cmd = cmd
self.run_num = run_num
def _rename_coverage_data(self, output, context): def _rename_coverage_data(self, out, sancov_dir):
"""Rename coverage data. """Rename coverage data.
Rename files with PIDs to files with unique test IDs, because the number Rename files with PIDs to files with unique test IDs, because the number
...@@ -159,41 +86,27 @@ class TestJob(Job): ...@@ -159,41 +86,27 @@ class TestJob(Job):
42 is the test ID and 1 is the attempt (the same test might be rerun on 42 is the test ID and 1 is the attempt (the same test might be rerun on
failures). failures).
""" """
if context.sancov_dir and output.pid is not None: if sancov_dir and out.pid is not None:
shell = self.test.suite.GetShellForTestCase(self.test) # Doesn't work on windows so basename is sufficient to get the shell name.
sancov_file = os.path.join( shell = os.path.basename(self.cmd.shell)
context.sancov_dir, "%s.%d.sancov" % (shell, output.pid)) sancov_file = os.path.join(sancov_dir, "%s.%d.sancov" % (shell, out.pid))
# Some tests are expected to fail and don't produce coverage data. # Some tests are expected to fail and don't produce coverage data.
if os.path.exists(sancov_file): if os.path.exists(sancov_file):
parts = sancov_file.split(".") parts = sancov_file.split(".")
new_sancov_file = ".".join( new_sancov_file = ".".join(
parts[:-2] + parts[:-2] +
["test", str(self.test.id), str(self.test.run)] + ["test", str(self.test_id), str(self.run_num)] +
parts[-1:] parts[-1:]
) )
assert not os.path.exists(new_sancov_file) assert not os.path.exists(new_sancov_file)
os.rename(sancov_file, new_sancov_file) os.rename(sancov_file, new_sancov_file)
def Run(self, process_context): def run(self, context):
try:
# Retrieve a new suite object on the worker-process side. The original
# suite object isn't pickled.
self.test.SetSuiteObject(process_context.suites)
instr = _GetInstructions(self.test, process_context.context)
except Exception, e:
# TODO(majeski): Better exception reporting.
return SetupProblem(e, self.test)
start_time = time.time() start_time = time.time()
output = commands.Execute(instr.command, instr.verbose, instr.timeout, out = self.cmd.execute()
instr.env) self._rename_coverage_data(out, context.sancov_dir)
self._rename_coverage_data(output, process_context.context) return (self.test_id, out, time.time() - start_time)
return (instr.id, output, time.time() - start_time)
def RunTest(job, process_context):
return job.Run(process_context)
class Runner(object): class Runner(object):
...@@ -262,7 +175,7 @@ class Runner(object): ...@@ -262,7 +175,7 @@ class Runner(object):
test.duration = None test.duration = None
test.output = None test.output = None
test.run += 1 test.run += 1
pool.add([TestJob(test)]) pool.add([TestJob(test.id, test.cmd, test.run)])
self.remaining += 1 self.remaining += 1
self.total += 1 self.total += 1
...@@ -327,7 +240,7 @@ class Runner(object): ...@@ -327,7 +240,7 @@ class Runner(object):
# remember the output for comparison. # remember the output for comparison.
test.run += 1 test.run += 1
test.output = result[1] test.output = result[1]
pool.add([TestJob(test)]) pool.add([TestJob(test.id, test.cmd, test.run)])
# Always update the perf database. # Always update the perf database.
return True return True
...@@ -350,7 +263,7 @@ class Runner(object): ...@@ -350,7 +263,7 @@ class Runner(object):
assert test.id >= 0 assert test.id >= 0
test_map[test.id] = test test_map[test.id] = test
try: try:
yield [TestJob(test)] yield [TestJob(test.id, test.cmd, test.run)]
except Exception, e: except Exception, e:
# If this failed, save the exception and re-raise it later (after # If this failed, save the exception and re-raise it later (after
# all other tests have had a chance to run). # all other tests have had a chance to run).
...@@ -358,10 +271,10 @@ class Runner(object): ...@@ -358,10 +271,10 @@ class Runner(object):
continue continue
try: try:
it = pool.imap_unordered( it = pool.imap_unordered(
fn=RunTest, fn=run_job,
gen=gen_tests(), gen=gen_tests(),
process_context_fn=MakeProcessContext, process_context_fn=MakeProcessContext,
process_context_args=[self.context, self.suite_names], process_context_args=[self.context.sancov_dir],
) )
for result in it: for result in it:
if result.heartbeat: if result.heartbeat:
...@@ -378,7 +291,7 @@ class Runner(object): ...@@ -378,7 +291,7 @@ class Runner(object):
self._VerbosePrint("Closing process pool.") self._VerbosePrint("Closing process pool.")
pool.terminate() pool.terminate()
self._VerbosePrint("Closing database connection.") self._VerbosePrint("Closing database connection.")
self._RunPerfSafe(lambda: self.perf_data_manager.close()) self._RunPerfSafe(self.perf_data_manager.close)
if self.perf_failures: if self.perf_failures:
# Nuke perf data in case of failures. This might not work on windows as # Nuke perf data in case of failures. This might not work on windows as
# some files might still be open. # some files might still be open.
...@@ -403,6 +316,8 @@ class Runner(object): ...@@ -403,6 +316,8 @@ class Runner(object):
class BreakNowException(Exception): class BreakNowException(Exception):
def __init__(self, value): def __init__(self, value):
super(BreakNowException, self).__init__()
self.value = value self.value = value
def __str__(self): def __str__(self):
return repr(self.value) return repr(self.value)
...@@ -120,8 +120,8 @@ class Pool(): ...@@ -120,8 +120,8 @@ class Pool():
self.done, self.done,
process_context_fn, process_context_fn,
process_context_args)) process_context_args))
self.processes.append(p)
p.start() p.start()
self.processes.append(p)
self.advance(gen) self.advance(gen)
while self.count > 0: while self.count > 0:
...@@ -145,6 +145,11 @@ class Pool(): ...@@ -145,6 +145,11 @@ class Pool():
else: else:
yield MaybeResult.create_result(result.result) yield MaybeResult.create_result(result.result)
self.advance(gen) self.advance(gen)
except KeyboardInterrupt:
raise
except Exception as e:
traceback.print_exc()
print(">>> EXCEPTION: %s" % e)
finally: finally:
self.terminate() self.terminate()
if internal_error: if internal_error:
......
...@@ -32,14 +32,10 @@ import os ...@@ -32,14 +32,10 @@ import os
import sys import sys
import time import time
from . import execution
from . import junit_output from . import junit_output
from . import statusfile from . import statusfile
ABS_PATH_PREFIX = os.getcwd() + os.sep
class ProgressIndicator(object): class ProgressIndicator(object):
def __init__(self): def __init__(self):
...@@ -70,18 +66,6 @@ class ProgressIndicator(object): ...@@ -70,18 +66,6 @@ class ProgressIndicator(object):
'negative': negative_marker 'negative': negative_marker
} }
def _EscapeCommand(self, test):
command, _ = execution.GetCommand(test, self.runner.context)
parts = []
for part in command:
if ' ' in part:
# Escape spaces. We may need to escape more characters for this
# to work properly.
parts.append('"%s"' % part)
else:
parts.append(part)
return " ".join(parts)
class IndicatorNotifier(object): class IndicatorNotifier(object):
"""Holds a list of progress indicators and notifies them all on events.""" """Holds a list of progress indicators and notifies them all on events."""
...@@ -123,7 +107,7 @@ class SimpleProgressIndicator(ProgressIndicator): ...@@ -123,7 +107,7 @@ class SimpleProgressIndicator(ProgressIndicator):
if failed.output.stdout: if failed.output.stdout:
print "--- stdout ---" print "--- stdout ---"
print failed.output.stdout.strip() print failed.output.stdout.strip()
print "Command: %s" % self._EscapeCommand(failed) print "Command: %s" % failed.cmd.to_string()
if failed.output.HasCrashed(): if failed.output.HasCrashed():
print "exit code: %d" % failed.output.exit_code print "exit code: %d" % failed.output.exit_code
print "--- CRASHED ---" print "--- CRASHED ---"
...@@ -205,7 +189,7 @@ class CompactProgressIndicator(ProgressIndicator): ...@@ -205,7 +189,7 @@ class CompactProgressIndicator(ProgressIndicator):
stderr = test.output.stderr.strip() stderr = test.output.stderr.strip()
if len(stderr): if len(stderr):
print self.templates['stderr'] % stderr print self.templates['stderr'] % stderr
print "Command: %s" % self._EscapeCommand(test) print "Command: %s" % test.cmd.to_string()
if test.output.HasCrashed(): if test.output.HasCrashed():
print "exit code: %d" % test.output.exit_code print "exit code: %d" % test.output.exit_code
print "--- CRASHED ---" print "--- CRASHED ---"
...@@ -273,6 +257,7 @@ class MonochromeProgressIndicator(CompactProgressIndicator): ...@@ -273,6 +257,7 @@ class MonochromeProgressIndicator(CompactProgressIndicator):
class JUnitTestProgressIndicator(ProgressIndicator): class JUnitTestProgressIndicator(ProgressIndicator):
def __init__(self, junitout, junittestsuite): def __init__(self, junitout, junittestsuite):
super(JUnitTestProgressIndicator, self).__init__()
self.outputter = junit_output.JUnitTestOutput(junittestsuite) self.outputter = junit_output.JUnitTestOutput(junittestsuite)
if junitout: if junitout:
self.outfile = open(junitout, "w") self.outfile = open(junitout, "w")
...@@ -293,7 +278,7 @@ class JUnitTestProgressIndicator(ProgressIndicator): ...@@ -293,7 +278,7 @@ class JUnitTestProgressIndicator(ProgressIndicator):
stderr = test.output.stderr.strip() stderr = test.output.stderr.strip()
if len(stderr): if len(stderr):
fail_text += "stderr:\n%s\n" % stderr fail_text += "stderr:\n%s\n" % stderr
fail_text += "Command: %s" % self._EscapeCommand(test) fail_text += "Command: %s" % self.test.cmd.to_string()
if test.output.HasCrashed(): if test.output.HasCrashed():
fail_text += "exit code: %d\n--- CRASHED ---" % test.output.exit_code fail_text += "exit code: %d\n--- CRASHED ---" % test.output.exit_code
if test.output.HasTimedOut(): if test.output.HasTimedOut():
...@@ -307,6 +292,7 @@ class JUnitTestProgressIndicator(ProgressIndicator): ...@@ -307,6 +292,7 @@ class JUnitTestProgressIndicator(ProgressIndicator):
class JsonTestProgressIndicator(ProgressIndicator): class JsonTestProgressIndicator(ProgressIndicator):
def __init__(self, json_test_results, arch, mode, random_seed): def __init__(self, json_test_results, arch, mode, random_seed):
super(JsonTestProgressIndicator, self).__init__()
self.json_test_results = json_test_results self.json_test_results = json_test_results
self.arch = arch self.arch = arch
self.mode = mode self.mode = mode
...@@ -334,7 +320,7 @@ class JsonTestProgressIndicator(ProgressIndicator): ...@@ -334,7 +320,7 @@ class JsonTestProgressIndicator(ProgressIndicator):
{ {
"name": test.GetLabel(), "name": test.GetLabel(),
"flags": test.flags, "flags": test.flags,
"command": self._EscapeCommand(test).replace(ABS_PATH_PREFIX, ""), "command": test.cmd.to_string(relative=True),
"duration": test.duration, "duration": test.duration,
"marked_slow": statusfile.IsSlow( "marked_slow": statusfile.IsSlow(
test.suite.GetStatusFileOutcomes(test)), test.suite.GetStatusFileOutcomes(test)),
...@@ -364,7 +350,7 @@ class JsonTestProgressIndicator(ProgressIndicator): ...@@ -364,7 +350,7 @@ class JsonTestProgressIndicator(ProgressIndicator):
self.results.append({ self.results.append({
"name": test.GetLabel(), "name": test.GetLabel(),
"flags": test.flags, "flags": test.flags,
"command": self._EscapeCommand(test).replace(ABS_PATH_PREFIX, ""), "command": test.cmd.to_string(relative=True),
"run": test.run, "run": test.run,
"stdout": test.output.stdout, "stdout": test.output.stdout,
"stderr": test.output.stderr, "stderr": test.output.stderr,
...@@ -384,6 +370,7 @@ class JsonTestProgressIndicator(ProgressIndicator): ...@@ -384,6 +370,7 @@ class JsonTestProgressIndicator(ProgressIndicator):
class FlakinessTestProgressIndicator(ProgressIndicator): class FlakinessTestProgressIndicator(ProgressIndicator):
def __init__(self, json_test_results): def __init__(self, json_test_results):
super(FlakinessTestProgressIndicator, self).__init__()
self.json_test_results = json_test_results self.json_test_results = json_test_results
self.results = {} self.results = {}
self.summary = { self.summary = {
......
...@@ -30,7 +30,7 @@ import fnmatch ...@@ -30,7 +30,7 @@ import fnmatch
import imp import imp
import os import os
from . import commands from . import command
from . import statusfile from . import statusfile
from . import utils from . import utils
from ..objects import testcase from ..objects import testcase
...@@ -310,6 +310,44 @@ class TestSuite(object): ...@@ -310,6 +310,44 @@ class TestSuite(object):
return self._outcomes_cache[cache_key] return self._outcomes_cache[cache_key]
def GetCommand(self, test, context):
shell = self.GetShellForTestCase(test)
shell_flags = []
if shell == 'd8':
shell_flags.append('--test')
if utils.IsWindows():
shell += '.exe'
if context.random_seed:
shell_flags.append('--random-seed=%s' % context.random_seed)
files, flags, env = self.GetParametersForTestCase(test, context)
return command.Command(
cmd_prefix=context.command_prefix,
shell=os.path.abspath(os.path.join(context.shell_dir, shell)),
args=(
shell_flags +
files +
context.extra_flags +
flags
),
env=env,
timeout=self.GetTimeout(test, context),
verbose=context.verbose
)
def GetTimeout(self, testcase, context):
timeout = context.timeout
if ("--stress-opt" in testcase.flags or
"--stress-opt" in context.mode_flags or
"--stress-opt" in context.extra_flags):
timeout *= 4
if "--noenable-vfp3" in context.extra_flags:
timeout *= 2
# TODO(majeski): make it slow outcome dependent.
timeout *= 2
return timeout
def GetShellForTestCase(self, testcase): def GetShellForTestCase(self, testcase):
"""Returns shell to be executed for this test case.""" """Returns shell to be executed for this test case."""
return 'd8' return 'd8'
...@@ -376,16 +414,15 @@ class GoogleTestSuite(TestSuite): ...@@ -376,16 +414,15 @@ class GoogleTestSuite(TestSuite):
output = None output = None
for i in xrange(3): # Try 3 times in case of errors. for i in xrange(3): # Try 3 times in case of errors.
cmd = ( cmd = command.Command(
context.command_prefix + cmd_prefix=context.command_prefix,
[shell, "--gtest_list_tests"] + shell=shell,
context.extra_flags args=['--gtest_list_tests'] + context.extra_flags)
) output = cmd.execute()
output = commands.Execute(cmd)
if output.exit_code == 0: if output.exit_code == 0:
break break
print "Test executable failed to list the tests (try %d).\n\nCmd:" % i print "Test executable failed to list the tests (try %d).\n\nCmd:" % i
print ' '.join(cmd) print cmd
print "\nStdout:" print "\nStdout:"
print output.stdout print output.stdout
print "\nStderr:" print "\nStderr:"
......
...@@ -36,13 +36,11 @@ class TestCase(object): ...@@ -36,13 +36,11 @@ class TestCase(object):
self.id = None # int, used to map result back to TestCase instance self.id = None # int, used to map result back to TestCase instance
self.duration = None # assigned during execution self.duration = None # assigned during execution
self.run = 1 # The nth time this test is executed. self.run = 1 # The nth time this test is executed.
self.cmd = None
def CopyAddingFlags(self, variant, flags): def CopyAddingFlags(self, variant, flags):
return TestCase(self.suite, self.path, variant, self.flags + flags) return TestCase(self.suite, self.path, variant, self.flags + flags)
def SetSuiteObject(self, suites):
self.suite = suites[self.suite]
def suitename(self): def suitename(self):
return self.suite.name return self.suite.name
......
...@@ -454,6 +454,7 @@ class StandardTestRunner(base_runner.BaseTestRunner): ...@@ -454,6 +454,7 @@ class StandardTestRunner(base_runner.BaseTestRunner):
for t in s.tests: for t in s.tests:
t.flags += s.GetStatusfileFlags(t) t.flags += s.GetStatusfileFlags(t)
t.cmd = s.GetCommand(t, ctx)
s.tests = self._shard_tests(s.tests, options) s.tests = self._shard_tests(s.tests, options)
num_tests += len(s.tests) num_tests += len(s.tests)
......
...@@ -94,8 +94,8 @@ class PerfTest(unittest.TestCase): ...@@ -94,8 +94,8 @@ class PerfTest(unittest.TestCase):
include=([os.path.join(cls.base, "run_perf.py")])) include=([os.path.join(cls.base, "run_perf.py")]))
cls._cov.start() cls._cov.start()
import run_perf import run_perf
from testrunner.local import commands from testrunner.local import command
global commands global command
global run_perf global run_perf
@classmethod @classmethod
...@@ -125,9 +125,14 @@ class PerfTest(unittest.TestCase): ...@@ -125,9 +125,14 @@ class PerfTest(unittest.TestCase):
stderr=None, stderr=None,
timed_out=kwargs.get("timed_out", False)) timed_out=kwargs.get("timed_out", False))
for arg in args[1]] for arg in args[1]]
def execute(*args, **kwargs): def create_cmd(*args, **kwargs):
return test_outputs.pop() cmd = MagicMock()
commands.Execute = MagicMock(side_effect=execute) def execute(*args, **kwargs):
return test_outputs.pop()
cmd.execute = MagicMock(side_effect=execute)
return cmd
command.Command = MagicMock(side_effect=create_cmd)
# Check that d8 is called from the correct cwd for each test run. # Check that d8 is called from the correct cwd for each test run.
dirs = [path.join(TEST_WORKSPACE, arg) for arg in args[0]] dirs = [path.join(TEST_WORKSPACE, arg) for arg in args[0]]
...@@ -164,18 +169,23 @@ class PerfTest(unittest.TestCase): ...@@ -164,18 +169,23 @@ class PerfTest(unittest.TestCase):
self.assertEquals(errors, self._LoadResults()["errors"]) self.assertEquals(errors, self._LoadResults()["errors"])
def _VerifyMock(self, binary, *args, **kwargs): def _VerifyMock(self, binary, *args, **kwargs):
arg = [path.join(path.dirname(self.base), binary)] shell = path.join(path.dirname(self.base), binary)
arg += args command.Command.assert_called_with(
commands.Execute.assert_called_with( cmd_prefix=[],
arg, timeout=kwargs.get("timeout", 60)) shell=shell,
args=list(args),
timeout=kwargs.get('timeout', 60))
def _VerifyMockMultiple(self, *args, **kwargs): def _VerifyMockMultiple(self, *args, **kwargs):
expected = [] self.assertEquals(len(args), len(command.Command.call_args_list))
for arg in args: for arg, actual in zip(args, command.Command.call_args_list):
a = [path.join(path.dirname(self.base), arg[0])] expected = {
a += arg[1:] 'cmd_prefix': [],
expected.append(((a,), {"timeout": kwargs.get("timeout", 60)})) 'shell': path.join(path.dirname(self.base), arg[0]),
self.assertEquals(expected, commands.Execute.call_args_list) 'args': list(arg[1:]),
'timeout': kwargs.get('timeout', 60)
}
self.assertEquals((expected, ), actual)
def testOneRun(self): def testOneRun(self):
self._WriteTestInput(V8_JSON) self._WriteTestInput(V8_JSON)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment