Commit 9f7d440e authored by Michal Majewski's avatar Michal Majewski Committed by Commit Bot

[test] Check output on the worker process.

I added additional exception logging in the execution.py since
errors in processing results were really difficult to debug.

There is a problem on Windows with class serialization when
it comes from dynamically loaded module. To fix it I moved all
output processors to the tools/testrunner/outproc/ and import
them in test/*/testcfg.py.

Bug: v8:6917
Change-Id: Ida604641d659b006e91faf1d56a37769ec47f5f3
Reviewed-on: https://chromium-review.googlesource.com/842784
Commit-Queue: Michał Majewski <majeski@google.com>
Reviewed-by: 's avatarMichael Achenbach <machenbach@chromium.org>
Cr-Commit-Position: refs/heads/master@{#50361}
parent 6328c565
...@@ -6,8 +6,8 @@ import os ...@@ -6,8 +6,8 @@ import os
from testrunner.local import testsuite from testrunner.local import testsuite
from testrunner.local import utils from testrunner.local import utils
from testrunner.objects import outproc
from testrunner.objects import testcase from testrunner.objects import testcase
from testrunner.outproc import base as outproc
PROTOCOL_TEST_JS = "protocol-test.js" PROTOCOL_TEST_JS = "protocol-test.js"
EXPECTED_SUFFIX = "-expected.txt" EXPECTED_SUFFIX = "-expected.txt"
......
...@@ -25,14 +25,13 @@ ...@@ -25,14 +25,13 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import itertools
import os import os
import re import re
from testrunner.local import testsuite from testrunner.local import testsuite
from testrunner.local import utils from testrunner.local import utils
from testrunner.objects import outproc
from testrunner.objects import testcase from testrunner.objects import testcase
from testrunner.outproc import message
INVALID_FLAGS = ["--enable-slow-asserts"] INVALID_FLAGS = ["--enable-slow-asserts"]
...@@ -103,57 +102,10 @@ class TestCase(testcase.TestCase): ...@@ -103,57 +102,10 @@ class TestCase(testcase.TestCase):
@property @property
def output_proc(self): def output_proc(self):
return OutProc(self.expected_outcomes, return message.OutProc(self.expected_outcomes,
os.path.join(self.suite.root, self.path), os.path.join(self.suite.root, self.path),
self._expected_fail()) self._expected_fail())
class OutProc(outproc.OutProc):
def __init__(self, expected_outcomes, basepath, expected_fail):
super(OutProc, self).__init__(expected_outcomes)
self._basepath = basepath
self._expected_fail = expected_fail
def _is_failure_output(self, output):
fail = output.exit_code != 0
if fail != self._expected_fail:
return True
expected_lines = []
# Can't use utils.ReadLinesFrom() here because it strips whitespace.
with open(self._basepath + '.out') as f:
for line in f:
if line.startswith("#") or not line.strip():
continue
expected_lines.append(line)
raw_lines = output.stdout.splitlines()
actual_lines = [ s for s in raw_lines if not self._ignore_line(s) ]
if len(expected_lines) != len(actual_lines):
return True
env = {
'basename': os.path.basename(self._basepath + '.js'),
}
for (expected, actual) in itertools.izip_longest(
expected_lines, actual_lines, fillvalue=''):
pattern = re.escape(expected.rstrip() % env)
pattern = pattern.replace('\\*', '.*')
pattern = pattern.replace('\\{NUMBER\\}', '\d+(?:\.\d*)?')
pattern = '^%s$' % pattern
if not re.match(pattern, actual):
return True
return False
def _ignore_line(self, string):
"""Ignore empty lines, valgrind output, Android output."""
return (
not string or
not string.strip() or
string.startswith("==") or
string.startswith("**") or
string.startswith("ANDROID")
)
def GetSuite(name, root): def GetSuite(name, root):
return TestSuite(name, root) return TestSuite(name, root)
...@@ -3,11 +3,10 @@ ...@@ -3,11 +3,10 @@
# found in the LICENSE file. # found in the LICENSE file.
import os import os
import difflib
from testrunner.local import testsuite from testrunner.local import testsuite
from testrunner.objects import outproc
from testrunner.objects import testcase from testrunner.objects import testcase
from testrunner.outproc import mkgrokdump
SHELL = 'mkgrokdump' SHELL = 'mkgrokdump'
...@@ -42,31 +41,7 @@ class TestCase(testcase.TestCase): ...@@ -42,31 +41,7 @@ class TestCase(testcase.TestCase):
@property @property
def output_proc(self): def output_proc(self):
return OutProc(self.expected_outcomes, self.suite.expected_path) return mkgrokdump.OutProc(self.expected_outcomes, self.suite.expected_path)
class OutProc(outproc.OutProc):
def __init__(self, expected_outcomes, expected_path):
super(OutProc, self).__init__(expected_outcomes)
self._expected_path = expected_path
def _is_failure_output(self, output):
with open(self._expected_path) as f:
expected = f.read()
expected_lines = expected.splitlines()
actual_lines = output.stdout.splitlines()
diff = difflib.unified_diff(expected_lines, actual_lines, lineterm="",
fromfile="expected_path")
diffstring = '\n'.join(diff)
if diffstring is not "":
if "generated from a non-shipping build" in output.stdout:
return False
if not "generated from a shipping build" in output.stdout:
output.stdout = "Unexpected output:\n\n" + output.stdout
return True
output.stdout = diffstring
return True
return False
def GetSuite(name, root): def GetSuite(name, root):
......
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
import os import os
from testrunner.local import testsuite from testrunner.local import testsuite
from testrunner.objects import outproc
from testrunner.objects import testcase from testrunner.objects import testcase
from testrunner.outproc import mozilla
EXCLUDED = ["CVS", ".svn"] EXCLUDED = ["CVS", ".svn"]
...@@ -110,37 +110,13 @@ class TestCase(testcase.TestCase): ...@@ -110,37 +110,13 @@ class TestCase(testcase.TestCase):
def output_proc(self): def output_proc(self):
if not self.expected_outcomes: if not self.expected_outcomes:
if self.path.endswith('-n'): if self.path.endswith('-n'):
return MOZILLA_PASS_NEGATIVE return mozilla.MOZILLA_PASS_NEGATIVE
return MOZILLA_PASS_DEFAULT return mozilla.MOZILLA_PASS_DEFAULT
if self.path.endswith('-n'): if self.path.endswith('-n'):
return NegOutProc(self.expected_outcomes) return mozilla.NegOutProc(self.expected_outcomes)
return OutProc(self.expected_outcomes) return mozilla.OutProc(self.expected_outcomes)
def _is_failure_output(self, output):
return (
output.exit_code != 0 or
'FAILED!' in output.stdout
)
class OutProc(outproc.OutProc):
"""Optimized for positive tests."""
OutProc._is_failure_output = _is_failure_output
class PassOutProc(outproc.PassOutProc):
"""Optimized for positive tests expected to PASS."""
PassOutProc._is_failure_output = _is_failure_output
NegOutProc = outproc.negative(OutProc)
NegPassOutProc = outproc.negative(PassOutProc)
MOZILLA_PASS_DEFAULT = PassOutProc()
MOZILLA_PASS_NEGATIVE = NegPassOutProc()
def GetSuite(name, root): def GetSuite(name, root):
return TestSuite(name, root) return TestSuite(name, root)
...@@ -33,12 +33,13 @@ import re ...@@ -33,12 +33,13 @@ import re
import sys import sys
import tarfile import tarfile
from testrunner.local import statusfile from testrunner.local import statusfile
from testrunner.local import testsuite from testrunner.local import testsuite
from testrunner.local import utils from testrunner.local import utils
from testrunner.objects import outproc
from testrunner.objects import testcase from testrunner.objects import testcase
from testrunner.outproc import base as outproc
from testrunner.outproc import test262
# TODO(littledan): move the flag mapping into the status file # TODO(littledan): move the flag mapping into the status file
FEATURE_FLAGS = { FEATURE_FLAGS = {
...@@ -246,57 +247,13 @@ class TestCase(testcase.TestCase): ...@@ -246,57 +247,13 @@ class TestCase(testcase.TestCase):
@property @property
def output_proc(self): def output_proc(self):
if self._expected_exception is not None: if self._expected_exception is not None:
return ExceptionOutProc(self.expected_outcomes, self._expected_exception) return test262.ExceptionOutProc(self.expected_outcomes,
self._expected_exception)
if self.expected_outcomes == outproc.OUTCOMES_PASS: if self.expected_outcomes == outproc.OUTCOMES_PASS:
return PASS_NO_EXCEPTION return test262.PASS_NO_EXCEPTION
return NoExceptionOutProc(self.expected_outcomes) return test262.NoExceptionOutProc(self.expected_outcomes)
class ExceptionOutProc(outproc.OutProc):
"""Output processor for tests with expected exception."""
def __init__(self, expected_outcomes, expected_exception=None):
super(ExceptionOutProc, self).__init__(expected_outcomes)
self._expected_exception = expected_exception
def _is_failure_output(self, output):
if output.exit_code != 0:
return True
if self._expected_exception != self._parse_exception(output.stdout):
return True
return 'FAILED!' in output.stdout
def _parse_exception(self, string):
# somefile:somelinenumber: someerror[: sometext]
# somefile might include an optional drive letter on windows e.g. "e:".
match = re.search(
'^(?:\w:)?[^:]*:[0-9]+: ([^: ]+?)($|: )', string, re.MULTILINE)
if match:
return match.group(1).strip()
else:
return None
def _is_failure_output(self, output):
return (
output.exit_code != 0 or
'FAILED!' in output.stdout
)
class NoExceptionOutProc(outproc.OutProc):
"""Output processor optimized for tests without expected exception."""
NoExceptionOutProc._is_failure_output = _is_failure_output
class PassNoExceptionOutProc(outproc.PassOutProc):
"""
Output processor optimized for tests expected to PASS without expected
exception.
"""
PassNoExceptionOutProc._is_failure_output = _is_failure_output
PASS_NO_EXCEPTION = PassNoExceptionOutProc()
def GetSuite(name, root): def GetSuite(name, root):
......
...@@ -29,8 +29,8 @@ import os ...@@ -29,8 +29,8 @@ import os
import re import re
from testrunner.local import testsuite from testrunner.local import testsuite
from testrunner.objects import outproc
from testrunner.objects import testcase from testrunner.objects import testcase
from testrunner.outproc import webkit
FILES_PATTERN = re.compile(r"//\s+Files:(.*)") FILES_PATTERN = re.compile(r"//\s+Files:(.*)")
SELF_SCRIPT_PATTERN = re.compile(r"//\s+Env: TEST_FILE_NAME") SELF_SCRIPT_PATTERN = re.compile(r"//\s+Env: TEST_FILE_NAME")
...@@ -104,23 +104,10 @@ class TestCase(testcase.TestCase): ...@@ -104,23 +104,10 @@ class TestCase(testcase.TestCase):
@property @property
def output_proc(self): def output_proc(self):
return OutProc( return webkit.OutProc(
self.expected_outcomes, self.expected_outcomes,
os.path.join(self.suite.root, self.path) + '-expected.txt') os.path.join(self.suite.root, self.path) + '-expected.txt')
class OutProc(outproc.ExpectedOutProc):
def _is_failure_output(self, output):
if output.exit_code != 0:
return True
return super(OutProc, self)._is_failure_output(output)
def _ignore_expected_line(self, line):
return (
line.startswith('#') or
super(OutProc, self)._ignore_expected_line(line)
)
def GetSuite(name, root): def GetSuite(name, root):
return TestSuite(name, root) return TestSuite(name, root)
...@@ -264,7 +264,6 @@ class DeoptFuzzer(base_runner.BaseTestRunner): ...@@ -264,7 +264,6 @@ class DeoptFuzzer(base_runner.BaseTestRunner):
True, # No sorting of test cases. True, # No sorting of test cases.
0, # Don't rerun failing tests. 0, # Don't rerun failing tests.
0, # No use of a rerun-failing-tests maximum. 0, # No use of a rerun-failing-tests maximum.
False, # No predictable mode.
False, # No no_harness mode. False, # No no_harness mode.
False, # Don't use perf data. False, # Don't use perf data.
False) # Coverage not supported. False) # Coverage not supported.
......
...@@ -179,7 +179,6 @@ class GCFuzzer(base_runner.BaseTestRunner): ...@@ -179,7 +179,6 @@ class GCFuzzer(base_runner.BaseTestRunner):
True, # No sorting of test cases. True, # No sorting of test cases.
0, # Don't rerun failing tests. 0, # Don't rerun failing tests.
0, # No use of a rerun-failing-tests maximum. 0, # No use of a rerun-failing-tests maximum.
False, # No predictable mode.
False, # No no_harness mode. False, # No no_harness mode.
False, # Don't use perf data. False, # Don't use perf data.
False) # Coverage not supported. False) # Coverage not supported.
...@@ -213,7 +212,7 @@ class GCFuzzer(base_runner.BaseTestRunner): ...@@ -213,7 +212,7 @@ class GCFuzzer(base_runner.BaseTestRunner):
for s in suites: for s in suites:
for t in s.tests: for t in s.tests:
# Skip failed tests. # Skip failed tests.
if s.HasUnexpectedOutput(t, runner.outputs[t]): if t.output_proc.has_unexpected_output(runner.outputs[t]):
print '%s failed, skipping' % t.path print '%s failed, skipping' % t.path
continue continue
max_limit = self._get_max_limit_reached(runner.outputs[t]) max_limit = self._get_max_limit_reached(runner.outputs[t])
......
...@@ -31,6 +31,7 @@ import os ...@@ -31,6 +31,7 @@ import os
import re import re
import shutil import shutil
import sys import sys
import traceback
from . import command from . import command
from . import perfdata from . import perfdata
...@@ -51,7 +52,7 @@ ProcessContext = collections.namedtuple( ...@@ -51,7 +52,7 @@ ProcessContext = collections.namedtuple(
TestJobResult = collections.namedtuple( TestJobResult = collections.namedtuple(
'TestJobResult', ['id', 'output']) 'TestJobResult', ['id', 'outproc_result'])
def MakeProcessContext(sancov_dir): def MakeProcessContext(sancov_dir):
return ProcessContext(sancov_dir) return ProcessContext(sancov_dir)
...@@ -74,9 +75,10 @@ class Job(object): ...@@ -74,9 +75,10 @@ class Job(object):
class TestJob(Job): class TestJob(Job):
def __init__(self, test_id, cmd, run_num): def __init__(self, test_id, cmd, outproc, run_num):
self.test_id = test_id self.test_id = test_id
self.cmd = cmd self.cmd = cmd
self.outproc = outproc
self.run_num = run_num self.run_num = run_num
def _rename_coverage_data(self, out, sancov_dir): def _rename_coverage_data(self, out, sancov_dir):
...@@ -105,20 +107,21 @@ class TestJob(Job): ...@@ -105,20 +107,21 @@ class TestJob(Job):
os.rename(sancov_file, new_sancov_file) os.rename(sancov_file, new_sancov_file)
def run(self, context): def run(self, context):
out = self.cmd.execute() output = self.cmd.execute()
self._rename_coverage_data(out, context.sancov_dir) self._rename_coverage_data(output, context.sancov_dir)
return TestJobResult(self.test_id, out) return TestJobResult(self.test_id, self.outproc.process(output))
class Runner(object): class Runner(object):
def __init__(self, suites, progress_indicator, context): def __init__(self, suites, progress_indicator, context, outproc_factory=None):
self.datapath = os.path.join("out", "testrunner_data") self.datapath = os.path.join("out", "testrunner_data")
self.perf_data_manager = perfdata.GetPerfDataManager( self.perf_data_manager = perfdata.GetPerfDataManager(
context, self.datapath) context, self.datapath)
self.perfdata = self.perf_data_manager.GetStore(context.arch, context.mode) self.perfdata = self.perf_data_manager.GetStore(context.arch, context.mode)
self.perf_failures = False self.perf_failures = False
self.printed_allocations = False self.printed_allocations = False
self.outproc_factory = outproc_factory or (lambda test: test.output_proc)
self.tests = [t for s in suites for t in s.tests] self.tests = [t for s in suites for t in s.tests]
# TODO(majeski): Pass dynamically instead of keeping them in the runner. # TODO(majeski): Pass dynamically instead of keeping them in the runner.
...@@ -159,7 +162,7 @@ class Runner(object): ...@@ -159,7 +162,7 @@ class Runner(object):
print("PerfData exception: %s" % e) print("PerfData exception: %s" % e)
self.perf_failures = True self.perf_failures = True
def _MaybeRerun(self, pool, test, job_result): def _MaybeRerun(self, pool, test, result):
if test.run <= self.context.rerun_failures_count: if test.run <= self.context.rerun_failures_count:
# Possibly rerun this test if its run count is below the maximum per # Possibly rerun this test if its run count is below the maximum per
# test. <= as the flag controls reruns not including the first run. # test. <= as the flag controls reruns not including the first run.
...@@ -172,23 +175,24 @@ class Runner(object): ...@@ -172,23 +175,24 @@ class Runner(object):
# reached. # reached.
return return
if (test.run >= 2 and if (test.run >= 2 and
job_result.output.duration > self.context.timeout / 20.0): result.output.duration > self.context.timeout / 20.0):
# Rerun slow tests at most once. # Rerun slow tests at most once.
return return
# Rerun this test. # Rerun this test.
test.run += 1 test.run += 1
pool.add([TestJob(test.id, test.cmd, test.run)]) pool.add([
TestJob(test.id, test.cmd, self.outproc_factory(test), test.run)
])
self.remaining += 1 self.remaining += 1
self.total += 1 self.total += 1
def _ProcessTest(self, test, job_result, pool): def _ProcessTest(self, test, result, pool):
self.outputs[test] = job_result.output self.outputs[test] = result.output
has_unexpected_output = test.suite.HasUnexpectedOutput( has_unexpected_output = result.has_unexpected_output
test, job_result.output, self.context)
if has_unexpected_output: if has_unexpected_output:
self.failed.append(test) self.failed.append(test)
if job_result.output.HasCrashed(): if result.output.HasCrashed():
self.crashed += 1 self.crashed += 1
else: else:
self.succeeded += 1 self.succeeded += 1
...@@ -196,12 +200,12 @@ class Runner(object): ...@@ -196,12 +200,12 @@ class Runner(object):
# For the indicator, everything that happens after the first run is treated # For the indicator, everything that happens after the first run is treated
# as unexpected even if it flakily passes in order to include it in the # as unexpected even if it flakily passes in order to include it in the
# output. # output.
self.indicator.HasRun(test, job_result.output, self.indicator.HasRun(test, result.output,
has_unexpected_output or test.run > 1) has_unexpected_output or test.run > 1)
if has_unexpected_output: if has_unexpected_output:
# Rerun test failures after the indicator has processed the results. # Rerun test failures after the indicator has processed the results.
self._VerbosePrint("Attempting to rerun test after failure.") self._VerbosePrint("Attempting to rerun test after failure.")
self._MaybeRerun(pool, test, job_result) self._MaybeRerun(pool, test, result)
# Update the perf database if the test succeeded. # Update the perf database if the test succeeded.
return not has_unexpected_output return not has_unexpected_output
...@@ -224,11 +228,13 @@ class Runner(object): ...@@ -224,11 +228,13 @@ 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.id, test.cmd, test.run)] yield [
TestJob(test.id, test.cmd, self.outproc_factory(test), 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).
queued_exception[0] = e queued_exception[0] = e, traceback.format_exc()
continue continue
try: try:
it = pool.imap_unordered( it = pool.imap_unordered(
...@@ -243,12 +249,19 @@ class Runner(object): ...@@ -243,12 +249,19 @@ class Runner(object):
continue continue
job_result = result.value job_result = result.value
test_id = job_result.id
outproc_result = job_result.outproc_result
test = test_map[job_result.id] test = test_map[test_id]
update_perf = self._ProcessTest(test, job_result, pool) update_perf = self._ProcessTest(test, outproc_result, pool)
if update_perf: if update_perf:
self._RunPerfSafe(lambda: self.perfdata.UpdatePerfData( self._RunPerfSafe(lambda: self.perfdata.UpdatePerfData(
test, job_result.output.duration)) test, outproc_result.output.duration))
except KeyboardInterrupt:
raise
except:
traceback.print_exc()
raise
finally: finally:
self._VerbosePrint("Closing process pool.") self._VerbosePrint("Closing process pool.")
pool.terminate() pool.terminate()
...@@ -260,7 +273,9 @@ class Runner(object): ...@@ -260,7 +273,9 @@ class Runner(object):
print "Deleting perf test data due to db corruption." print "Deleting perf test data due to db corruption."
shutil.rmtree(self.datapath) shutil.rmtree(self.datapath)
if queued_exception[0]: if queued_exception[0]:
raise queued_exception[0] e, stacktrace = queued_exception[0]
print stacktrace
raise e
def _VerbosePrint(self, text): def _VerbosePrint(self, text):
if self.context.verbose: if self.context.verbose:
......
...@@ -171,20 +171,6 @@ class TestSuite(object): ...@@ -171,20 +171,6 @@ class TestSuite(object):
break break
self.tests = filtered self.tests = filtered
def HasUnexpectedOutput(self, test, output, ctx=None):
if ctx and ctx.predictable:
# Only check the exit code of the predictable_wrapper in
# verify-predictable mode. Negative tests are not supported as they
# usually also don't print allocation hashes. There are two versions of
# negative tests: one specified by the test, the other specified through
# the status file (e.g. known bugs).
return (
output.exit_code != 0 and
not test.output_proc.negative and
statusfile.FAIL not in test.expected_outcomes
)
return test.output_proc.has_unexpected_output(output)
def _create_test(self, path, **kwargs): def _create_test(self, path, **kwargs):
test = self._test_class()(self, path, self._path_to_name(path), **kwargs) test = self._test_class()(self, path, self._path_to_name(path), **kwargs)
return test return test
......
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
class Context(): class Context():
def __init__(self, arch, mode, shell_dir, mode_flags, verbose, timeout, def __init__(self, arch, mode, shell_dir, mode_flags, verbose, timeout,
isolates, command_prefix, extra_flags, noi18n, random_seed, isolates, command_prefix, extra_flags, noi18n, random_seed,
no_sorting, rerun_failures_count, rerun_failures_max, no_sorting, rerun_failures_count, rerun_failures_max, no_harness,
predictable, no_harness, use_perf_data, sancov_dir): use_perf_data, sancov_dir):
self.arch = arch self.arch = arch
self.mode = mode self.mode = mode
self.shell_dir = shell_dir self.shell_dir = shell_dir
...@@ -45,7 +45,6 @@ class Context(): ...@@ -45,7 +45,6 @@ class Context():
self.no_sorting = no_sorting self.no_sorting = no_sorting
self.rerun_failures_count = rerun_failures_count self.rerun_failures_count = rerun_failures_count
self.rerun_failures_max = rerun_failures_max self.rerun_failures_max = rerun_failures_max
self.predictable = predictable
self.no_harness = no_harness self.no_harness = no_harness
self.use_perf_data = use_perf_data self.use_perf_data = use_perf_data
self.sancov_dir = sancov_dir self.sancov_dir = sancov_dir
# 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 ..local import statusfile
from ..outproc import base as outproc_base
# Only check the exit code of the predictable_wrapper in
# verify-predictable mode. Negative tests are not supported as they
# usually also don't print allocation hashes. There are two versions of
# negative tests: one specified by the test, the other specified through
# the status file (e.g. known bugs).
def get_outproc(test):
output_proc = test.output_proc
if output_proc.negative or statusfile.FAIL in test.expected_outcomes:
# TODO(majeski): Skip these tests instead of having special outproc.
return NeverUnexpectedOutputOutProc(output_proc)
return OutProc(output_proc)
class OutProc(outproc_base.BaseOutProc):
"""Output processor wrapper for predictable mode. It has custom
has_unexpected_output implementation, but for all other methods it simply
calls wrapped output processor.
"""
def __init__(self, _outproc):
super(OutProc, self).__init__()
self._outproc = _outproc
def process(self, output):
return self._outproc.process(output)
def has_unexpected_output(self, output):
return output.exit_code != 0
def get_outcome(self, output):
return self._outproc.get_outcome(output)
@property
def negative(self):
return self._outproc.negative
@property
def expected_outcomes(self):
return self._outproc.expected_outcomes
class NeverUnexpectedOutputOutProc(OutProc):
"""Output processor wrapper for tests that we will return False for
has_unexpected_output in the predictable mode.
"""
def has_unexpected_output(self, output):
return False
...@@ -30,7 +30,7 @@ import os ...@@ -30,7 +30,7 @@ import os
import re import re
import shlex import shlex
from . import outproc from ..outproc import base as outproc
from ..local import command from ..local import command
from ..local import statusfile from ..local import statusfile
from ..local import utils from ..local import utils
......
# Copyright 2018 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.
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import collections
import itertools import itertools
from ..local import statusfile from ..local import statusfile
...@@ -10,8 +11,13 @@ from ..local import statusfile ...@@ -10,8 +11,13 @@ from ..local import statusfile
OUTCOMES_PASS = [statusfile.PASS] OUTCOMES_PASS = [statusfile.PASS]
OUTCOMES_FAIL = [statusfile.FAIL] OUTCOMES_FAIL = [statusfile.FAIL]
Result = collections.namedtuple('Result', ['has_unexpected_output', 'output'])
class BaseOutProc(object): class BaseOutProc(object):
def process(self, output):
return Result(self.has_unexpected_output(output), output)
def has_unexpected_output(self, output): def has_unexpected_output(self, output):
return self.get_outcome(output) not in self.expected_outcomes return self.get_outcome(output) not in self.expected_outcomes
...@@ -43,12 +49,10 @@ class BaseOutProc(object): ...@@ -43,12 +49,10 @@ class BaseOutProc(object):
raise NotImplementedError() raise NotImplementedError()
def negative(cls): class Negative(object):
class Neg(cls):
@property @property
def negative(self): def negative(self):
return True return True
return Neg
class PassOutProc(BaseOutProc): class PassOutProc(BaseOutProc):
......
# Copyright 2018 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.
import itertools
import os
import re
from . import base
class OutProc(base.OutProc):
def __init__(self, expected_outcomes, basepath, expected_fail):
super(OutProc, self).__init__(expected_outcomes)
self._basepath = basepath
self._expected_fail = expected_fail
def _is_failure_output(self, output):
fail = output.exit_code != 0
if fail != self._expected_fail:
return True
expected_lines = []
# Can't use utils.ReadLinesFrom() here because it strips whitespace.
with open(self._basepath + '.out') as f:
for line in f:
if line.startswith("#") or not line.strip():
continue
expected_lines.append(line)
raw_lines = output.stdout.splitlines()
actual_lines = [ s for s in raw_lines if not self._ignore_line(s) ]
if len(expected_lines) != len(actual_lines):
return True
env = {
'basename': os.path.basename(self._basepath + '.js'),
}
for (expected, actual) in itertools.izip_longest(
expected_lines, actual_lines, fillvalue=''):
pattern = re.escape(expected.rstrip() % env)
pattern = pattern.replace('\\*', '.*')
pattern = pattern.replace('\\{NUMBER\\}', '\d+(?:\.\d*)?')
pattern = '^%s$' % pattern
if not re.match(pattern, actual):
return True
return False
def _ignore_line(self, string):
"""Ignore empty lines, valgrind output, Android output."""
return (
not string or
not string.strip() or
string.startswith("==") or
string.startswith("**") or
string.startswith("ANDROID")
)
# Copyright 2018 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.
import difflib
from . import base
class OutProc(base.OutProc):
def __init__(self, expected_outcomes, expected_path):
super(OutProc, self).__init__(expected_outcomes)
self._expected_path = expected_path
def _is_failure_output(self, output):
with open(self._expected_path) as f:
expected = f.read()
expected_lines = expected.splitlines()
actual_lines = output.stdout.splitlines()
diff = difflib.unified_diff(expected_lines, actual_lines, lineterm="",
fromfile="expected_path")
diffstring = '\n'.join(diff)
if diffstring is not "":
if "generated from a non-shipping build" in output.stdout:
return False
if not "generated from a shipping build" in output.stdout:
output.stdout = "Unexpected output:\n\n" + output.stdout
return True
output.stdout = diffstring
return True
return False
# Copyright 2018 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 . import base
def _is_failure_output(self, output):
return (
output.exit_code != 0 or
'FAILED!' in output.stdout
)
class OutProc(base.OutProc):
"""Optimized for positive tests."""
OutProc._is_failure_output = _is_failure_output
class PassOutProc(base.PassOutProc):
"""Optimized for positive tests expected to PASS."""
PassOutProc._is_failure_output = _is_failure_output
class NegOutProc(base.Negative, OutProc):
pass
class NegPassOutProc(base.Negative, PassOutProc):
pass
MOZILLA_PASS_DEFAULT = PassOutProc()
MOZILLA_PASS_NEGATIVE = NegPassOutProc()
# Copyright 2018 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.
import re
from . import base
class ExceptionOutProc(base.OutProc):
"""Output processor for tests with expected exception."""
def __init__(self, expected_outcomes, expected_exception=None):
super(ExceptionOutProc, self).__init__(expected_outcomes)
self._expected_exception = expected_exception
def _is_failure_output(self, output):
if output.exit_code != 0:
return True
if self._expected_exception != self._parse_exception(output.stdout):
return True
return 'FAILED!' in output.stdout
def _parse_exception(self, string):
# somefile:somelinenumber: someerror[: sometext]
# somefile might include an optional drive letter on windows e.g. "e:".
match = re.search(
'^(?:\w:)?[^:]*:[0-9]+: ([^: ]+?)($|: )', string, re.MULTILINE)
if match:
return match.group(1).strip()
else:
return None
def _is_failure_output(self, output):
return (
output.exit_code != 0 or
'FAILED!' in output.stdout
)
class NoExceptionOutProc(base.OutProc):
"""Output processor optimized for tests without expected exception."""
NoExceptionOutProc._is_failure_output = _is_failure_output
class PassNoExceptionOutProc(base.PassOutProc):
"""
Output processor optimized for tests expected to PASS without expected
exception.
"""
PassNoExceptionOutProc._is_failure_output = _is_failure_output
PASS_NO_EXCEPTION = PassNoExceptionOutProc()
# Copyright 2018 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 . import base
class OutProc(base.ExpectedOutProc):
def _is_failure_output(self, output):
if output.exit_code != 0:
return True
return super(OutProc, self)._is_failure_output(output)
def _ignore_expected_line(self, line):
return (
line.startswith('#') or
super(OutProc, self)._ignore_expected_line(line)
)
...@@ -25,6 +25,7 @@ from testrunner.local import utils ...@@ -25,6 +25,7 @@ from testrunner.local import utils
from testrunner.local import verbose from testrunner.local import verbose
from testrunner.local.variants import ALL_VARIANTS from testrunner.local.variants import ALL_VARIANTS
from testrunner.objects import context from testrunner.objects import context
from testrunner.objects import predictable
TIMEOUT_DEFAULT = 60 TIMEOUT_DEFAULT = 60
...@@ -377,7 +378,6 @@ class StandardTestRunner(base_runner.BaseTestRunner): ...@@ -377,7 +378,6 @@ class StandardTestRunner(base_runner.BaseTestRunner):
options.no_sorting, options.no_sorting,
options.rerun_failures_count, options.rerun_failures_count,
options.rerun_failures_max, options.rerun_failures_max,
self.build_config.predictable,
options.no_harness, options.no_harness,
use_perf_data=not options.swarming, use_perf_data=not options.swarming,
sancov_dir=self.sancov_dir) sancov_dir=self.sancov_dir)
...@@ -495,7 +495,12 @@ class StandardTestRunner(base_runner.BaseTestRunner): ...@@ -495,7 +495,12 @@ class StandardTestRunner(base_runner.BaseTestRunner):
progress_indicator.Register(progress.FlakinessTestProgressIndicator( progress_indicator.Register(progress.FlakinessTestProgressIndicator(
options.flakiness_results)) options.flakiness_results))
runner = execution.Runner(suites, progress_indicator, ctx) if self.build_config.predictable:
outproc_factory = predictable.get_outproc
else:
outproc_factory = None
runner = execution.Runner(suites, progress_indicator, ctx,
outproc_factory)
exit_code = runner.Run(options.j) exit_code = runner.Run(options.j)
overall_duration = time.time() - start_time overall_duration = time.time() - start_time
......
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