Commit 5af38295 authored by Michael Arntzenius's avatar Michael Arntzenius

update tester infrastructure to support new CPython tests

parent fd785dc1
...@@ -187,6 +187,8 @@ add_test(NAME check-format COMMAND ${CMAKE_SOURCE_DIR}/tools/check_format.sh ${L ...@@ -187,6 +187,8 @@ add_test(NAME check-format COMMAND ${CMAKE_SOURCE_DIR}/tools/check_format.sh ${L
add_test(NAME gc_unittest COMMAND gc_unittest) add_test(NAME gc_unittest COMMAND gc_unittest)
add_test(NAME analysis_unittest COMMAND analysis_unittest) add_test(NAME analysis_unittest COMMAND analysis_unittest)
add_test(NAME pyston_defaults COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/tools/tester.py -R ./pyston -j${TEST_THREADS} -a=-S -k ${CMAKE_SOURCE_DIR}/test/tests) add_test(NAME pyston_defaults COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/tools/tester.py -R ./pyston -j${TEST_THREADS} -a=-S -k ${CMAKE_SOURCE_DIR}/test/tests)
# we pass -I to cpython tests and skip failing ones b/c they are slooow otherwise
add_test(NAME pyston_defaults_cpython_tests COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/tools/tester.py -R ./pyston -j${TEST_THREADS} -a=-S -a=-I -k --exit-code-only --skip-failing ${CMAKE_SOURCE_DIR}/test/cpython)
add_test(NAME pyston_max_compilation_tier COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/tools/tester.py -R ./pyston -j${TEST_THREADS} -a=-O -a=-S -k ${CMAKE_SOURCE_DIR}/test/tests) add_test(NAME pyston_max_compilation_tier COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/tools/tester.py -R ./pyston -j${TEST_THREADS} -a=-O -a=-S -k ${CMAKE_SOURCE_DIR}/test/tests)
add_test(NAME pyston_experimental_pypa_parser COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/tools/tester.py -a=-x -R ./pyston -j${TEST_THREADS} -a=-n -a=-S -k ${CMAKE_SOURCE_DIR}/test/tests) add_test(NAME pyston_experimental_pypa_parser COMMAND ${PYTHON_EXE} ${CMAKE_SOURCE_DIR}/tools/tester.py -a=-x -R ./pyston -j${TEST_THREADS} -a=-n -a=-S -k ${CMAKE_SOURCE_DIR}/test/tests)
......
...@@ -490,6 +490,8 @@ check: ...@@ -490,6 +490,8 @@ check:
$(MAKE) ext_python ext_pyston pyston_dbg $(MAKE) ext_python ext_pyston pyston_dbg
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_dbg -j$(TEST_THREADS) -k -a=-S $(TESTS_DIR) $(ARGS) $(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_dbg -j$(TEST_THREADS) -k -a=-S $(TESTS_DIR) $(ARGS)
@# we pass -I to cpython tests & skip failing ones because they are sloooow otherwise
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_dbg -j$(TEST_THREADS) -k -a=-S -a=-I --exit-code-only --skip-failing $(TEST_DIR)/cpython $(ARGS)
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_dbg -j$(TEST_THREADS) -k -a=-n -a=-x -a=-S $(TESTS_DIR) $(ARGS) $(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_dbg -j$(TEST_THREADS) -k -a=-n -a=-x -a=-S $(TESTS_DIR) $(ARGS)
@# skip -O for dbg @# skip -O for dbg
...@@ -504,6 +506,8 @@ check: ...@@ -504,6 +506,8 @@ check:
@# It can be useful to test release mode, since it actually exposes different functionality @# It can be useful to test release mode, since it actually exposes different functionality
@# since we can make different decisions about which internal functions to inline or not. @# since we can make different decisions about which internal functions to inline or not.
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_release -j$(TEST_THREADS) -k -a=-S $(TESTS_DIR) $(ARGS) $(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_release -j$(TEST_THREADS) -k -a=-S $(TESTS_DIR) $(ARGS)
@# we pass -I to cpython tests and skip failing ones because they are sloooow otherwise
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_release -j$(TEST_THREADS) -k -a=-S -a=-I --exit-code-only --skip-failing $(TEST_DIR)/cpython $(ARGS)
@# skip -n for dbg @# skip -n for dbg
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_release -j$(TEST_THREADS) -k -a=-O -a=-x -a=-S $(TESTS_DIR) $(ARGS) $(PYTHON) $(TOOLS_DIR)/tester.py -R pyston_release -j$(TEST_THREADS) -k -a=-O -a=-x -a=-S $(TESTS_DIR) $(ARGS)
...@@ -916,7 +920,8 @@ clean: ...@@ -916,7 +920,8 @@ clean:
# ex instead of saying "make tests/run_1", I can just write "make run_1" # ex instead of saying "make tests/run_1", I can just write "make run_1"
define make_search define make_search
$(eval \ $(eval \
$1: $(TEST_DIR)/tests/$1 ; $1: $(TESTS_DIR)/$1 ;
$1: $(TEST_DIR)/cpython/$1 ;
$1: ./microbenchmarks/$1 ; $1: ./microbenchmarks/$1 ;
$1: ./minibenchmarks/$1 ; $1: ./minibenchmarks/$1 ;
$1: ./benchmarks/$1 ; $1: ./benchmarks/$1 ;
...@@ -932,6 +937,8 @@ $(eval \ ...@@ -932,6 +937,8 @@ $(eval \
.PHONY: test$1 check$1 .PHONY: test$1 check$1
check$1 test$1: $(PYTHON_EXE_DEPS) pyston$1 ext_pyston check$1 test$1: $(PYTHON_EXE_DEPS) pyston$1 ext_pyston
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston$1 -j$(TEST_THREADS) -a=-S -k $(TESTS_DIR) $(ARGS) $(PYTHON) $(TOOLS_DIR)/tester.py -R pyston$1 -j$(TEST_THREADS) -a=-S -k $(TESTS_DIR) $(ARGS)
@# we pass -I to cpython tests and skip failing ones because they are sloooow otherwise
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston$1 -j$(TEST_THREADS) -a=-S -a=-I -k --exit-code-only --skip-failing $(TEST_DIR)/cpython $(ARGS)
$(PYTHON) $(TOOLS_DIR)/tester.py -a=-x -R pyston$1 -j$(TEST_THREADS) -a=-n -a=-S -k $(TESTS_DIR) $(ARGS) $(PYTHON) $(TOOLS_DIR)/tester.py -a=-x -R pyston$1 -j$(TEST_THREADS) -a=-n -a=-S -k $(TESTS_DIR) $(ARGS)
$(PYTHON) $(TOOLS_DIR)/tester.py -R pyston$1 -j$(TEST_THREADS) -a=-O -a=-S -k $(TESTS_DIR) $(ARGS) $(PYTHON) $(TOOLS_DIR)/tester.py -R pyston$1 -j$(TEST_THREADS) -a=-O -a=-S -k $(TESTS_DIR) $(ARGS)
......
...@@ -346,25 +346,19 @@ def _is_gui_available(): ...@@ -346,25 +346,19 @@ def _is_gui_available():
def is_resource_enabled(resource): def is_resource_enabled(resource):
"""Test whether a resource is enabled. Known resources are set by """Test whether a resource is enabled. Known resources are set by
regrtest.py.""" regrtest.py."""
# Pyston change: we assume that resources are not available in general
return use_resources is not None and resource in use_resources return use_resources is not None and resource in use_resources
def requires(resource, msg=None): def requires(resource, msg=None):
"""Raise ResourceDenied if the specified resource is not available. """Raise ResourceDenied if the specified resource is not available."""
If the caller's module is __main__ then automatically return True. The
possibility of False being returned occurs when regrtest.py is executing."""
if resource == 'gui' and not _is_gui_available(): if resource == 'gui' and not _is_gui_available():
raise ResourceDenied(_is_gui_available.reason) raise ResourceDenied(_is_gui_available.reason)
# see if the caller's module is __main__ - if so, treat as if # Pyston change: we don't check if the caller's module is __main__ using sys._getframe() magic.
# the resource was set
if sys._getframe(1).f_globals.get("__name__") == "__main__":
return
if not is_resource_enabled(resource): if not is_resource_enabled(resource):
if msg is None: if msg is None:
msg = "Use of the `%s' resource not enabled" % resource msg = "Use of the `%s' resource not enabled" % resource
raise ResourceDenied(msg) raise ResourceDenied(msg)
# Don't use "localhost", since resolving it uses the DNS under recent # Don't use "localhost", since resolving it uses the DNS under recent
# Windows versions (see issue #18792). # Windows versions (see issue #18792).
HOST = "127.0.0.1" HOST = "127.0.0.1"
...@@ -506,6 +500,11 @@ try: ...@@ -506,6 +500,11 @@ try:
except NameError: except NameError:
have_unicode = False have_unicode = False
requires_unicode = unittest.skipUnless(have_unicode, 'no unicode support')
def u(s):
return unicode(s, 'unicode-escape')
is_jython = sys.platform.startswith('java') is_jython = sys.platform.startswith('java')
# FS_NONASCII: non-ASCII Unicode character encodable by # FS_NONASCII: non-ASCII Unicode character encodable by
...@@ -749,42 +748,49 @@ class WarningsRecorder(object): ...@@ -749,42 +748,49 @@ class WarningsRecorder(object):
def _filterwarnings(filters, quiet=False): def _filterwarnings(filters, quiet=False):
"""Catch the warnings, then check if all the expected # Pyston change:
warnings have been raised and re-raise unexpected warnings. # this bare yield seems to work for now, but we might need to yield up a WarningsRecorder in some cases?
If 'quiet' is True, only re-raise the unexpected warnings. yield
"""
# TODO: Frame introspection in Pyston?
# old code follows:
# """Catch the warnings, then check if all the expected
# warnings have been raised and re-raise unexpected warnings.
# If 'quiet' is True, only re-raise the unexpected warnings.
# """
# Clear the warning registry of the calling module # Clear the warning registry of the calling module
# in order to re-raise the warnings. # in order to re-raise the warnings.
frame = sys._getframe(2) # frame = sys._getframe(2)
registry = frame.f_globals.get('__warningregistry__') # registry = frame.f_globals.get('__warningregistry__')
if registry: # if registry:
registry.clear() # registry.clear()
with warnings.catch_warnings(record=True) as w: # with warnings.catch_warnings(record=True) as w:
# Set filter "always" to record all warnings. Because # # Set filter "always" to record all warnings. Because
# test_warnings swap the module, we need to look up in # # test_warnings swap the module, we need to look up in
# the sys.modules dictionary. # # the sys.modules dictionary.
sys.modules['warnings'].simplefilter("always") # sys.modules['warnings'].simplefilter("always")
yield WarningsRecorder(w) # yield WarningsRecorder(w)
# Filter the recorded warnings # # Filter the recorded warnings
reraise = [warning.message for warning in w] # reraise = [warning.message for warning in w]
missing = [] # missing = []
for msg, cat in filters: # for msg, cat in filters:
seen = False # seen = False
for exc in reraise[:]: # for exc in reraise[:]:
message = str(exc) # message = str(exc)
# Filter out the matching messages # # Filter out the matching messages
if (re.match(msg, message, re.I) and # if (re.match(msg, message, re.I) and
issubclass(exc.__class__, cat)): # issubclass(exc.__class__, cat)):
seen = True # seen = True
reraise.remove(exc) # reraise.remove(exc)
if not seen and not quiet: # if not seen and not quiet:
# This filter caught nothing # # This filter caught nothing
missing.append((msg, cat.__name__)) # missing.append((msg, cat.__name__))
if reraise: # if reraise:
raise AssertionError("unhandled warning %r" % reraise[0]) # raise AssertionError("unhandled warning %r" % reraise[0])
if missing: # if missing:
raise AssertionError("filter (%r, %s) did not catch any warning" % # raise AssertionError("filter (%r, %s) did not catch any warning" %
missing[0]) # missing[0])
@contextlib.contextmanager @contextlib.contextmanager
......
...@@ -139,7 +139,10 @@ class TestLoader(object): ...@@ -139,7 +139,10 @@ class TestLoader(object):
hasattr(getattr(testCaseClass, attrname), '__call__') hasattr(getattr(testCaseClass, attrname), '__call__')
testFnNames = filter(isTestMethod, dir(testCaseClass)) testFnNames = filter(isTestMethod, dir(testCaseClass))
if self.sortTestMethodsUsing: if self.sortTestMethodsUsing:
testFnNames.sort(key=_CmpToKey(self.sortTestMethodsUsing)) # Pyston change:
# TODO(rntz): needs builtin `cmp` to work
#testFnNames.sort(key=_CmpToKey(self.sortTestMethodsUsing))
testFnNames.sort()
return testFnNames return testFnNames
def discover(self, start_dir, pattern='test*.py', top_level_dir=None): def discover(self, start_dir, pattern='test*.py', top_level_dir=None):
......
...@@ -153,15 +153,18 @@ class TestResult(object): ...@@ -153,15 +153,18 @@ class TestResult(object):
"""Converts a sys.exc_info()-style tuple of values into a string.""" """Converts a sys.exc_info()-style tuple of values into a string."""
exctype, value, tb = err exctype, value, tb = err
# Skip test runner traceback levels # Skip test runner traceback levels
while tb and self._is_relevant_tb_level(tb): # Pyston change: I've commented this out for now. - rntz
tb = tb.tb_next # TODO(rntz): needs traceback stuff to work
# while tb and self._is_relevant_tb_level(tb):
if exctype is test.failureException: # tb = tb.tb_next
# Skip assert*() traceback levels
length = self._count_relevant_tb_levels(tb) # if exctype is test.failureException:
msgLines = traceback.format_exception(exctype, value, tb, length) # # Skip assert*() traceback levels
else: # length = self._count_relevant_tb_levels(tb)
msgLines = traceback.format_exception(exctype, value, tb) # msgLines = traceback.format_exception(exctype, value, tb, length)
# else:
# msgLines = traceback.format_exception(exctype, value, tb)
msgLines = traceback.format_exception(exctype, value, tb)
if self.buffer: if self.buffer:
output = sys.stdout.getvalue() output = sys.stdout.getvalue()
......
# skip-if: True
# This is a test to make sure that the stack space we allocate # This is a test to make sure that the stack space we allocate
# for "long arg" calls (ie calls that take more than 4 arguments) # for "long arg" calls (ie calls that take more than 4 arguments)
# gets restored. # gets restored.
......
# skip-if: True
# I was worried about using a recursive parser for obscenely-nested source code, # I was worried about using a recursive parser for obscenely-nested source code,
# but based off this example it looks like that's what cPython and pypy both use as well. # but based off this example it looks like that's what cPython and pypy both use as well.
......
# Copyright (c) 2014-2015 Dropbox, Inc. # Copyright (c) 2014-2015 Dropbox, Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
...@@ -37,6 +37,9 @@ KEEP_GOING = False ...@@ -37,6 +37,9 @@ KEEP_GOING = False
FN_JUST_SIZE = 20 FN_JUST_SIZE = 20
EXTRA_JIT_ARGS = [] EXTRA_JIT_ARGS = []
TIME_LIMIT = 25 TIME_LIMIT = 25
TESTS_TO_SKIP = []
EXIT_CODE_ONLY = False
SKIP_FAILING_TESTS = False
# For fun, can test pypy. # For fun, can test pypy.
# Tough because the tester will check to see if the error messages are exactly the # Tough because the tester will check to see if the error messages are exactly the
...@@ -55,6 +58,7 @@ def set_ulimits(): ...@@ -55,6 +58,7 @@ def set_ulimits():
EXTMODULE_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../test/test_extension/build/lib.linux-x86_64-2.7/") EXTMODULE_DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../test/test_extension/build/lib.linux-x86_64-2.7/")
THIS_FILE = os.path.abspath(__file__) THIS_FILE = os.path.abspath(__file__)
_global_mtime = None _global_mtime = None
def get_global_mtime(): def get_global_mtime():
global _global_mtime global _global_mtime
...@@ -124,19 +128,39 @@ def canonicalize_stderr(stderr): ...@@ -124,19 +128,39 @@ def canonicalize_stderr(stderr):
return stderr return stderr
failed = [] failed = []
class Options(object): pass
# returns a single string, or a tuple of strings that are spliced together (with spaces between) by our caller
def run_test(fn, check_stats, run_memcheck): def run_test(fn, check_stats, run_memcheck):
r = os.path.basename(fn).rjust(FN_JUST_SIZE) opts = get_test_options(fn, check_stats, run_memcheck)
test_base_name = os.path.basename(fn).split('.')[0] del check_stats, run_memcheck
if test_base_name in TESTS_TO_SKIP: if opts.skip:
return r + " (skipped due to command line option)" return "(skipped: %s)" % opts.skip
statchecks = [] run_args = [os.path.abspath(IMAGE)] + opts.jit_args + [fn]
jit_args = ["-rq"] + EXTRA_JIT_ARGS start = time.time()
collect_stats = True p = subprocess.Popen(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=open("/dev/null"), preexec_fn=set_ulimits)
expected = "success" out, stderr = p.communicate()
should_error = False code = p.wait()
allow_warnings = [] elapsed = time.time() - start
return determine_test_result(fn, opts, code, out, stderr, elapsed)
def get_test_options(fn, check_stats, run_memcheck):
opts = Options()
opts.check_stats = check_stats
opts.run_memcheck = run_memcheck
opts.statchecks = []
opts.jit_args = ["-rq"] + EXTRA_JIT_ARGS
opts.collect_stats = True
opts.expected = "success"
opts.should_error = False
opts.allow_warnings = []
opts.skip = None
for l in open(fn): for l in open(fn):
l = l.strip() l = l.strip()
if not l: if not l:
...@@ -145,56 +169,54 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -145,56 +169,54 @@ def run_test(fn, check_stats, run_memcheck):
break break
if l.startswith("# statcheck:"): if l.startswith("# statcheck:"):
l = l[len("# statcheck:"):].strip() l = l[len("# statcheck:"):].strip()
statchecks.append(l) opts.statchecks.append(l)
elif l.startswith("# run_args:"): elif l.startswith("# run_args:"):
l = l[len("# run_args:"):].split() l = l[len("# run_args:"):].split()
jit_args += l opts.jit_args += l
elif l.startswith("# expected:"): elif l.startswith("# expected:"):
expected = l[len("# expected:"):].strip() opts.expected = l[len("# expected:"):].strip()
elif l.startswith("# should_error"): elif l.startswith("# should_error"):
should_error = True opts.should_error = True
elif l.startswith("# fail-if:"): elif l.startswith("# fail-if:"):
condition = l.split(':', 1)[1].strip() condition = l.split(':', 1)[1].strip()
if eval(condition): if eval(condition):
expected = "fail" opts.expected = "fail"
elif l.startswith("# skip-if:"): elif l.startswith("# skip-if:"):
skip_if = l[len("# skip-if:"):].strip() skip_if = l[len("# skip-if:"):].strip()
skip = eval(skip_if) if eval(skip_if):
if skip: opts.skip = "skip-if: %s" % skip_if[:30]
return r + " (skipped due to 'skip-if: %s')" % skip_if[:30]
elif fn.split('.')[0] in TESTS_TO_SKIP:
return r + " (skipped due to command line option)"
elif l.startswith("# allow-warning:"): elif l.startswith("# allow-warning:"):
allow_warnings.append("Warning: " + l.split(':', 1)[1].strip()) opts.allow_warnings.append("Warning: " + l.split(':', 1)[1].strip())
elif l.startswith("# no-collect-stats"): elif l.startswith("# no-collect-stats"):
collect_stats = False opts.collect_stats = False
if TEST_PYPY: if not opts.skip:
collect_stats = False # consider other reasons for skipping file
if SKIP_FAILING_TESTS and opts.expected == 'fail':
opts.skip = 'expected to fail'
elif os.path.basename(fn).split('.')[0] in TESTS_TO_SKIP:
opts.skip = 'command line option'
if collect_stats: if opts.collect_stats:
jit_args = ['-s'] + jit_args opts.jit_args = ['-s'] + opts.jit_args
assert expected in ("success", "fail", "statfail"), expected assert opts.expected in ("success", "fail", "statfail"), opts.expected
if TEST_PYPY: if TEST_PYPY:
jit_args = [] opts.jit_args = []
check_stats = False opts.collect_stats = False
expected = "success" opts.check_stats = False
opts.expected = "success"
run_args = [os.path.abspath(IMAGE)] + jit_args + [fn] return opts
start = time.time()
p = subprocess.Popen(run_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=open("/dev/null"), preexec_fn=set_ulimits)
out, stderr = p.communicate()
last_stderr_line = stderr.strip().split('\n')[-1]
code = p.wait() def determine_test_result(fn, opts, code, out, stderr, elapsed):
elapsed = time.time() - start last_stderr_line = stderr.strip().split('\n')[-1]
if allow_warnings: if opts.allow_warnings:
out_lines = [] out_lines = []
for l in out.split('\n'): for l in out.split('\n'):
for regex in allow_warnings: for regex in opts.allow_warnings:
if re.match(regex, l): if re.match(regex, l):
break break
else: else:
...@@ -202,7 +224,7 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -202,7 +224,7 @@ def run_test(fn, check_stats, run_memcheck):
out = "\n".join(out_lines) out = "\n".join(out_lines)
stats = None stats = None
if code >= 0 and collect_stats: if code >= 0 and opts.collect_stats:
stats = {} stats = {}
assert out.count("Stats:") == 1 assert out.count("Stats:") == 1
out, stats_str = out.split("Stats:") out, stats_str = out.split("Stats:")
...@@ -210,7 +232,14 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -210,7 +232,14 @@ def run_test(fn, check_stats, run_memcheck):
k, v = l.split(':') k, v = l.split(':')
stats[k.strip()] = int(v) stats[k.strip()] = int(v)
expected_code, expected_out, expected_err = get_expected_output(fn) if EXIT_CODE_ONLY:
# fools the rest of this function into thinking the output is OK & just checking the exit code.
# there oughtta be a cleaner way to do this.
expected_code, expected_out, expected_err = 0, out, stderr
else:
# run CPython to get the expected output
expected_code, expected_out, expected_err = get_expected_output(fn)
if code != expected_code: if code != expected_code:
color = 31 # red color = 31 # red
...@@ -227,17 +256,15 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -227,17 +256,15 @@ def run_test(fn, check_stats, run_memcheck):
else: else:
msg = "Exited with code %d (expected code %d)" % (code, expected_code) msg = "Exited with code %d (expected code %d)" % (code, expected_code)
if expected == "fail": if opts.expected == "fail":
r += " Expected failure (got code %d, should be %d)" % (code, expected_code) return "Expected failure (got code %d, should be %d)" % (code, expected_code)
return r
elif KEEP_GOING: elif KEEP_GOING:
r += " \033[%dmFAILED\033[0m (%s)" % (color, msg)
failed.append(fn) failed.append(fn)
return r return "\033[%dmFAILED\033[0m (%s)" % (color, msg)
else: else:
raise Exception("%s\n%s\n%s" % (msg, err, stderr)) raise Exception("%s\n%s\n%s" % (msg, err, stderr))
elif should_error == (code == 0): elif opts.should_error == (code == 0):
color = 31 # red color = 31 # red
if code == 0: if code == 0:
msg = "Exited successfully; remove '# should_error' if this is expected" msg = "Exited successfully; remove '# should_error' if this is expected"
...@@ -245,23 +272,20 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -245,23 +272,20 @@ def run_test(fn, check_stats, run_memcheck):
msg = "Exited with code %d; add '# should_error' if this is expected" % code msg = "Exited with code %d; add '# should_error' if this is expected" % code
if KEEP_GOING: if KEEP_GOING:
r += " \033[%dmFAILED\033[0m (%s)" % (color, msg)
failed.append(fn) failed.append(fn)
return r return "\033[%dmFAILED\033[0m (%s)" % (color, msg)
else: else:
# show last line of stderr so we have some idea went wrong # show last line of stderr so we have some idea went wrong
print "Last line of stderr: " + last_stderr_line print "Last line of stderr: " + last_stderr_line
raise Exception(msg) raise Exception(msg)
elif out != expected_out: elif out != expected_out:
if expected == "fail": if opts.expected == "fail":
r += " Expected failure (bad output)" return "Expected failure (bad output)"
return r
else: else:
if KEEP_GOING: if KEEP_GOING:
r += " \033[31mFAILED\033[0m (bad output)"
failed.append(fn) failed.append(fn)
return r return "\033[31mFAILED\033[0m (bad output)"
exp_fd, exp_fn = tempfile.mkstemp(prefix="expected_") exp_fd, exp_fn = tempfile.mkstemp(prefix="expected_")
out_fd, out_fn = tempfile.mkstemp(prefix="received_") out_fd, out_fn = tempfile.mkstemp(prefix="received_")
os.fdopen(exp_fd, 'w').write(expected_out) os.fdopen(exp_fd, 'w').write(expected_out)
...@@ -273,38 +297,34 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -273,38 +297,34 @@ def run_test(fn, check_stats, run_memcheck):
os.unlink(out_fn) os.unlink(out_fn)
raise Exception("Failed on %s:\n%s" % (fn, diff)) raise Exception("Failed on %s:\n%s" % (fn, diff))
elif not TEST_PYPY and canonicalize_stderr(stderr) != canonicalize_stderr(expected_err): elif not TEST_PYPY and canonicalize_stderr(stderr) != canonicalize_stderr(expected_err):
if expected == "fail": if opts.expected == "fail":
r += " Expected failure (bad stderr)" return "Expected failure (bad stderr)"
return r
elif KEEP_GOING: elif KEEP_GOING:
r += " \033[31mFAILED\033[0m (bad stderr)"
failed.append(fn) failed.append(fn)
return r return "\033[31mFAILED\033[0m (bad stderr)"
else: else:
raise Exception((canonicalize_stderr(stderr), canonicalize_stderr(expected_err))) raise Exception((canonicalize_stderr(stderr), canonicalize_stderr(expected_err)))
elif expected == "fail": elif opts.expected == "fail":
if KEEP_GOING: if KEEP_GOING:
r += " \033[31mFAILED\033[0m (unexpected success)"
failed.append(fn) failed.append(fn)
return r return "\033[31mFAILED\033[0m (unexpected success)"
raise Exception("Unexpected success on %s" % fn) raise Exception("Unexpected success on %s" % fn)
r += " Correct output (%5.1fms)" % (elapsed * 1000,) r = ("Correct output (%5.1fms)" % (elapsed * 1000,),)
if check_stats: if opts.check_stats:
def noninit_count(s): def noninit_count(s):
return stats.get(s, 0) - stats.get("_init_" + s, 0) return stats.get(s, 0) - stats.get("_init_" + s, 0)
for l in statchecks: for l in opts.statchecks:
test = eval(l) test = eval(l)
if not test: if not test:
if expected == "statfail": if opts.expected == "statfail":
r += " (expected statfailure)" r += ("(expected statfailure)",)
break break
elif KEEP_GOING: elif KEEP_GOING:
r += " \033[31mFailed statcheck\033[0m"
failed.append(fn) failed.append(fn)
return r return r + ("\033[31mFailed statcheck\033[0m",)
else: else:
m = re.match("""stats\[['"]([\w_]+)['"]]""", l) m = re.match("""stats\[['"]([\w_]+)['"]]""", l)
if m: if m:
...@@ -319,17 +339,16 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -319,17 +339,16 @@ def run_test(fn, check_stats, run_memcheck):
raise Exception((l, stats)) raise Exception((l, stats))
else: else:
# only can get here if all statchecks passed # only can get here if all statchecks passed
if expected == "statfail": if opts.expected == "statfail":
if KEEP_GOING: if KEEP_GOING:
r += " \033[31mUnexpected statcheck success\033[0m"
failed.append(fn) failed.append(fn)
return r return r + ("\033[31mUnexpected statcheck success\033[0m",)
else: else:
raise Exception(("Unexpected statcheck success!", statchecks, stats)) raise Exception(("Unexpected statcheck success!", statchecks, stats))
else: else:
r += " (ignoring stats)" r += ("(ignoring stats)",)
if run_memcheck: if opts.run_memcheck:
if code == 0: if code == 0:
start = time.time() start = time.time()
p = subprocess.Popen(["valgrind", "--tool=memcheck", "--leak-check=no"] + run_args, stdout=open("/dev/null", 'w'), stderr=subprocess.PIPE, stdin=open("/dev/null")) p = subprocess.Popen(["valgrind", "--tool=memcheck", "--leak-check=no"] + run_args, stdout=open("/dev/null", 'w'), stderr=subprocess.PIPE, stdin=open("/dev/null"))
...@@ -337,16 +356,15 @@ def run_test(fn, check_stats, run_memcheck): ...@@ -337,16 +356,15 @@ def run_test(fn, check_stats, run_memcheck):
assert p.wait() == 0 assert p.wait() == 0
if "Invalid read" not in err: if "Invalid read" not in err:
elapsed = (time.time() - start) elapsed = (time.time() - start)
r += " Memcheck passed (%4.1fs)" % (elapsed,) r += ("Memcheck passed (%4.1fs)" % (elapsed,),)
else: else:
if KEEP_GOING: if KEEP_GOING:
r += " \033[31mMEMCHECKS FAILED\033[0m"
failed.append(fn) failed.append(fn)
return r return r + ("\033[31mMEMCHECKS FAILED\033[0m",)
else: else:
raise Exception(err) raise Exception(err)
else: else:
r += " (Skipping memchecks) " r += ("(Skipping memchecks)",)
return r return r
...@@ -394,9 +412,13 @@ parser.add_argument('-t', '--time-limit', type=int, default=TIME_LIMIT, ...@@ -394,9 +412,13 @@ parser.add_argument('-t', '--time-limit', type=int, default=TIME_LIMIT,
help='set time limit in seconds for each test') help='set time limit in seconds for each test')
parser.add_argument('-s', '--skip-tests', type=str, default='', parser.add_argument('-s', '--skip-tests', type=str, default='',
help='tests to skip (comma-separated)') help='tests to skip (comma-separated)')
parser.add_argument('-e', '--exit-code-only', action='store_true',
help="only check exit code; don't run CPython to get expected output to compare against")
parser.add_argument('--skip-failing', action='store_true',
help="skip tests expected to fail")
parser.add_argument('test_dir') parser.add_argument('test_dir')
parser.add_argument('patterns', nargs='*') parser.add_argument('pattern', nargs='*')
def main(orig_dir): def main(orig_dir):
global KEEP_GOING global KEEP_GOING
...@@ -406,6 +428,8 @@ def main(orig_dir): ...@@ -406,6 +428,8 @@ def main(orig_dir):
global TEST_DIR global TEST_DIR
global FN_JUST_SIZE global FN_JUST_SIZE
global TESTS_TO_SKIP global TESTS_TO_SKIP
global EXIT_CODE_ONLY
global SKIP_FAILING_TESTS
run_memcheck = False run_memcheck = False
start = 1 start = 1
...@@ -418,47 +442,21 @@ def main(orig_dir): ...@@ -418,47 +442,21 @@ def main(orig_dir):
EXTRA_JIT_ARGS += opts.extra_args EXTRA_JIT_ARGS += opts.extra_args
TIME_LIMIT = opts.time_limit TIME_LIMIT = opts.time_limit
TESTS_TO_SKIP = opts.skip_tests.split(',') TESTS_TO_SKIP = opts.skip_tests.split(',')
EXIT_CODE_ONLY = opts.exit_code_only
SKIP_FAILING_TESTS = opts.skip_failing
TEST_DIR = os.path.join(orig_dir, opts.test_dir) TEST_DIR = os.path.join(orig_dir, opts.test_dir)
patterns = opts.patterns patterns = opts.pattern
assert os.path.isdir(TEST_DIR), "%s doesn't look like a directory with tests in it" % TEST_DIR assert os.path.isdir(TEST_DIR), "%s doesn't look like a directory with tests in it" % TEST_DIR
TOSKIP = ["%s/%s.py" % (TEST_DIR, i) for i in ( if TEST_DIR.rstrip('/').endswith("cpython") and not EXIT_CODE_ONLY:
"tuple_depth", print >>sys.stderr, "Test directory name ends in cpython; are you sure you don't want --exit-code-only?"
"longargs_stackusage",
)]
IGNORE_STATS = ["%s/%d.py" % (TEST_DIR, i) for i in ( # do we need this any more?
)] + [ IGNORE_STATS = ["%s/%d.py" % (TEST_DIR, i) for i in ()] + []
]
def _addto(l, tests): tests = [t for t in glob.glob("%s/*.py" % TEST_DIR)]
if isinstance(tests, str):
tests = [tests]
for t in tests:
l.append("%s/%s.py" % (TEST_DIR, t))
skip = functools.partial(_addto, TOSKIP)
if not patterns:
skip(["t", "t2"])
def tryParse(s):
if s.isdigit():
return int(s)
return s
def key(name):
i = tryParse(name)
if i < start:
return i + 100000
return i
tests = sorted([t for t in glob.glob("%s/*.py" % TEST_DIR)], key=lambda t:key(t[6:-3]))
tests += [
]
big_tests = [
]
tests += big_tests
LIB_DIR = os.path.join(sys.prefix, "lib/python2.7") LIB_DIR = os.path.join(sys.prefix, "lib/python2.7")
for t in tests: for t in tests:
...@@ -471,18 +469,16 @@ def main(orig_dir): ...@@ -471,18 +469,16 @@ def main(orig_dir):
module_name in sys.builtin_module_names: module_name in sys.builtin_module_names:
raise Exception("Error: %s hides builtin module '%s'" % (t, module_name)) raise Exception("Error: %s hides builtin module '%s'" % (t, module_name))
for t in TOSKIP:
assert t in ("%s/t.py" % TEST_DIR, "%s/t2.py" % TEST_DIR) or t in tests, t
if patterns: if patterns:
filtered_tests = [] filtered_tests = []
for t in tests: for t in tests:
if any(re.match("%s/%s.*\.py" % (TEST_DIR, p), t) for p in patterns): if any(re.match(os.path.join(TEST_DIR, p) + ".*\.py", t) for p in patterns):
filtered_tests.append(t) filtered_tests.append(t)
tests = filtered_tests tests = filtered_tests
if not tests: if not tests:
print >>sys.stderr, "No tests specified!" print >>sys.stderr, "No tests matched the given patterns. OK by me!"
sys.exit(1) # this can happen legitimately in e.g. `make check_test_foo` if test_foo.py is a CPython regression test.
sys.exit(0)
FN_JUST_SIZE = max(20, 2 + max(len(os.path.basename(fn)) for fn in tests)) FN_JUST_SIZE = max(20, 2 + max(len(os.path.basename(fn)) for fn in tests))
...@@ -493,8 +489,6 @@ def main(orig_dir): ...@@ -493,8 +489,6 @@ def main(orig_dir):
tests.sort(key=fileSize) tests.sort(key=fileSize)
for fn in tests: for fn in tests:
if fn in TOSKIP:
continue
check_stats = fn not in IGNORE_STATS check_stats = fn not in IGNORE_STATS
q.put((fn, check_stats, run_memcheck)) q.put((fn, check_stats, run_memcheck))
...@@ -507,11 +501,6 @@ def main(orig_dir): ...@@ -507,11 +501,6 @@ def main(orig_dir):
q.put(None) q.put(None)
for fn in tests: for fn in tests:
if fn in TOSKIP:
print os.path.basename(fn).rjust(FN_JUST_SIZE),
print " Skipping"
continue
with cv: with cv:
while fn not in results: while fn not in results:
try: try:
...@@ -527,7 +516,11 @@ def main(orig_dir): ...@@ -527,7 +516,11 @@ def main(orig_dir):
print "(%s also failed)" % fn print "(%s also failed)" % fn
sys.exit(1) sys.exit(1)
break break
print results[fn] name = os.path.basename(fn).rjust(FN_JUST_SIZE)
msgs = results[fn]
if isinstance(msgs,str):
msgs = [msgs]
print ' '.join([name] + list(msgs))
for t in threads: for t in threads:
t.join() t.join()
......
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