summaryrefslogtreecommitdiff
path: root/testsuite/driver
diff options
context:
space:
mode:
Diffstat (limited to 'testsuite/driver')
-rw-r--r--testsuite/driver/runtests.py50
-rw-r--r--testsuite/driver/testglobals.py6
-rw-r--r--testsuite/driver/testlib.py48
3 files changed, 73 insertions, 31 deletions
diff --git a/testsuite/driver/runtests.py b/testsuite/driver/runtests.py
index 39689c6255..33b432fd6d 100644
--- a/testsuite/driver/runtests.py
+++ b/testsuite/driver/runtests.py
@@ -276,10 +276,24 @@ else:
# set stdout to unbuffered (is this the best way to do it?)
sys.stdout = os.fdopen(sys.__stdout__.fileno(), "w", 0)
-tempdir = tempfile.mkdtemp('', 'ghctest-')
+if config.local:
+ tempdir = ''
+else:
+ # See note [Running tests in /tmp]
+ tempdir = tempfile.mkdtemp('', 'ghctest-')
+
+ # opts.testdir should be quoted when used, to make sure the testsuite
+ # keeps working when it contains backward slashes, for example from
+ # using os.path.join. Windows native and mingw* python
+ # (/mingw64/bin/python) set `os.path.sep = '\\'`, while msys2 python
+ # (/bin/python, /usr/bin/python or /usr/local/bin/python) sets
+ # `os.path.sep = '/'`.
+ # To catch usage of unquoted opts.testdir early, insert some spaces into
+ # tempdir.
+ tempdir = os.path.join(tempdir, 'test spaces')
def cleanup_and_exit(exitcode):
- if config.cleanup:
+ if config.cleanup and tempdir:
shutil.rmtree(tempdir, ignore_errors=True)
exit(exitcode)
@@ -334,3 +348,35 @@ else:
summary(t, open(config.summary_file, 'w'))
cleanup_and_exit(0)
+
+# Note [Running tests in /tmp]
+#
+# Use LOCAL=0 to run tests in /tmp, to catch tests that use files from
+# the source directory without copying them to the test directory first.
+#
+# As an example, take a run_command test with a Makefile containing
+# `$(TEST_HC) ../Foo.hs`. GHC will now create the output files Foo.o and
+# Foo.hi in the source directory. There are 2 problems with this:
+# * Output files in the source directory won't get cleaned up automatically.
+# * Two tests might (over)write the same output file.
+#
+# Tests that only fail when run concurrently with other tests are the
+# worst, so we try to catch them early by enabling LOCAL=0 in validate.
+#
+# Adding -outputdir='.' to TEST_HC_OPTS would help a bit, but it requires
+# making changes to quite a few tests. The problem is that
+# `$(TEST_HC) ../Foo.hs -outputdir=.` with Foo.hs containing
+# `module Main where` does not produce Foo.o, as it would without
+# -outputdir, but Main.o. See [1].
+#
+# Using -outputdir='.' is not foolproof anyway, since it does not change
+# the destination of the final executable (Foo.exe).
+#
+# Another hardening method that could be tried is to `chmod -w` the
+# source directory.
+#
+# By default we set LOCAL=1, because it makes it easier to inspect the
+# test directory while working on a new test.
+#
+# [1]
+# https://downloads.haskell.org/~ghc/8.0.1/docs/html/users_guide/separate_compilation.html#output-files
diff --git a/testsuite/driver/testglobals.py b/testsuite/driver/testglobals.py
index 17aa6d36b8..d08141f251 100644
--- a/testsuite/driver/testglobals.py
+++ b/testsuite/driver/testglobals.py
@@ -274,6 +274,12 @@ class TestOptions:
self.compile_timeout_multiplier = 1.0
self.run_timeout_multiplier = 1.0
+ self.cleanup = True
+
+ # Sould we run tests in a local subdirectory (<testname>-run) or
+ # in temporary directory in /tmp? See Note [Running tests in /tmp].
+ self.local = True
+
# The default set of options
global default_testopts
default_testopts = TestOptions()
diff --git a/testsuite/driver/testlib.py b/testsuite/driver/testlib.py
index f6db8288fa..32b6951fe9 100644
--- a/testsuite/driver/testlib.py
+++ b/testsuite/driver/testlib.py
@@ -546,25 +546,6 @@ def executeSetups(fs, name, opts):
# The current directory of tests
def newTestDir(tempdir, dir):
- # opts.testdir should be quoted when used, to make sure the testsuite
- # keeps working when it contains backward slashes, for example from
- # using os.path.join. Windows native and mingw* python
- # (/mingw64/bin/python) set `os.path.sep = '\\'`, while msys2 python
- # (/bin/python, /usr/bin/python or /usr/local/bin/python) sets
- # `os.path.sep = '/'`.
- # To catch usage of unquoted opts.testdir early, insert some spaces into
- # tempdir.
- tempdir = os.path.join(tempdir, 'test spaces')
-
- # Hack. A few tests depend on files in ancestor directories
- # (e.g. extra_files(['../../../../libraries/base/dist-install/haddock.t']))
- # Make sure tempdir is sufficiently "deep", such that copying/linking those
- # files won't cause any problems.
- #
- # If you received a framework failure about adding an extra level:
- # * add one extra '../' to the startswith('../../../../../') in do_test
- # * add one more number here:
- tempdir = os.path.join(tempdir, '1', '2', '3')
global thisdir_settings
# reset the options for this test directory
@@ -572,10 +553,12 @@ def newTestDir(tempdir, dir):
return _newTestDir(name, opts, tempdir, dir)
thisdir_settings = settings
+# Should be equal to entry in toplevel .gitignore.
+testdir_suffix = '.run'
def _newTestDir(name, opts, tempdir, dir):
opts.srcdir = os.path.join(os.getcwd(), dir)
- opts.testdir = os.path.join(tempdir, dir, name)
+ opts.testdir = os.path.join(tempdir, dir, name + testdir_suffix)
opts.compiler_always_flags = config.compiler_always_flags
# -----------------------------------------------------------------------------
@@ -718,13 +701,10 @@ def test_common_work (name, opts, func, args):
# this seems to be necessary for only about 10% of all
# tests).
files = set((f for f in os.listdir(opts.srcdir)
- if f.startswith(name)))
+ if f.startswith(name) and
+ not f.endswith(testdir_suffix)))
for filename in (opts.extra_files + extra_src_files.get(name, [])):
- if filename.startswith('../../../../../../'):
- framework_fail(name, 'whole-test',
- 'add extra level to testlib.py:newTestDir for: ' + filename)
-
- elif filename.startswith('/'):
+ if filename.startswith('/'):
framework_fail(name, 'whole-test',
'no absolute paths in extra_files please: ' + filename)
@@ -790,8 +770,18 @@ def do_test(name, way, func, args, files):
# would otherwise (accidentally) write to the same output file.
# It also makes it easier to keep the testsuite clean.
- for filename in files:
- src = in_srcdir(filename)
+ for extra_file in files:
+ src = in_srcdir(extra_file)
+ if extra_file.startswith('..'):
+ # In case the extra_file is a file in an ancestor
+ # directory (e.g. extra_files(['../shell.hs'])), make
+ # sure it is copied to the test directory
+ # (testdir/shell.hs), instead of ending up somewhere
+ # else in the tree (testdir/../shell.hs)
+ filename = os.path.basename(extra_file)
+ else:
+ filename = extra_file
+ assert not '..' in filename # no funny stuff (foo/../../bar)
dst = in_testdir(filename)
if os.path.isfile(src):
@@ -821,7 +811,7 @@ def do_test(name, way, func, args, files):
pass
else:
framework_fail(name, way,
- 'extra_file does not exist: ' + filename)
+ 'extra_file does not exist: ' + extra_file)
if not files:
# Always create the testdir, even when no files were copied