Features/spack test refactor cmds (#18518)
* no clean -t option, use 'spack test remove' * refactor commands to make better use of TestSuite objects
This commit is contained in:

committed by
Tamara Dahlgren

parent
102b91203a
commit
19e226259c
@@ -47,10 +47,7 @@ def setup_parser(subparser):
|
||||
'-p', '--python-cache', action='store_true',
|
||||
help="remove .pyc, .pyo files and __pycache__ folders")
|
||||
subparser.add_argument(
|
||||
'-t', '--test-stage', action='store_true',
|
||||
help="remove all files in Spack test stage")
|
||||
subparser.add_argument(
|
||||
'-a', '--all', action=AllClean, help="equivalent to -sdfmpt", nargs=0
|
||||
'-a', '--all', action=AllClean, help="equivalent to -sdfmp", nargs=0
|
||||
)
|
||||
arguments.add_common_arguments(subparser, ['specs'])
|
||||
|
||||
@@ -58,7 +55,7 @@ def setup_parser(subparser):
|
||||
def clean(parser, args):
|
||||
# If nothing was set, activate the default
|
||||
if not any([args.specs, args.stage, args.downloads, args.failures,
|
||||
args.misc_cache, args.test_stage, args.python_cache]):
|
||||
args.misc_cache, args.python_cache]):
|
||||
args.stage = True
|
||||
|
||||
# Then do the cleaning falling through the cases
|
||||
@@ -86,11 +83,6 @@ def clean(parser, args):
|
||||
tty.msg('Removing cached information on repositories')
|
||||
spack.caches.misc_cache.destroy()
|
||||
|
||||
if args.test_stage:
|
||||
tty.msg("Removing files in test stage")
|
||||
test_remove_args = collections.namedtuple('args', ['name'])(None)
|
||||
spack.cmd.test.test_remove(test_remove_args)
|
||||
|
||||
if args.python_cache:
|
||||
tty.msg('Removing python cache files')
|
||||
for directory in [lib_path, var_path]:
|
||||
|
@@ -7,13 +7,12 @@
|
||||
import os
|
||||
import argparse
|
||||
import textwrap
|
||||
import datetime
|
||||
import fnmatch
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import llnl.util.tty as tty
|
||||
import llnl.util.filesystem as fs
|
||||
|
||||
import spack.install_test
|
||||
import spack.environment as ev
|
||||
@@ -33,10 +32,9 @@ def setup_parser(subparser):
|
||||
# Run
|
||||
run_parser = sp.add_parser('run', help=test_run.__doc__)
|
||||
|
||||
name_help_msg = "Name the test for subsequent access."
|
||||
name_help_msg += " Default is the timestamp of the run formatted"
|
||||
name_help_msg += " 'YYYY-MM-DD_HH:MM:SS'"
|
||||
run_parser.add_argument('-n', '--name', help=name_help_msg)
|
||||
alias_help_msg = "Provide an alias for this test-suite"
|
||||
alias_help_msg += " for subsequent access."
|
||||
run_parser.add_argument('--alias', help=alias_help_msg)
|
||||
|
||||
run_parser.add_argument(
|
||||
'--fail-fast', action='store_true',
|
||||
@@ -88,19 +86,30 @@ def setup_parser(subparser):
|
||||
'filter', nargs=argparse.REMAINDER,
|
||||
help='optional case-insensitive glob patterns to filter results.')
|
||||
|
||||
# Find
|
||||
find_parser = sp.add_parser('find', help=test_find.__doc__)
|
||||
find_parser.add_argument(
|
||||
'filter', nargs=argparse.REMAINDER,
|
||||
help='optional case-insensitive glob patterns to filter results.')
|
||||
|
||||
# Status
|
||||
status_parser = sp.add_parser('status', help=test_status.__doc__)
|
||||
status_parser.add_argument('name', help="Test for which to provide status")
|
||||
status_parser.add_argument(
|
||||
'names', nargs=argparse.REMAINDER,
|
||||
help="Test suites for which to print status")
|
||||
|
||||
# Results
|
||||
results_parser = sp.add_parser('results', help=test_results.__doc__)
|
||||
results_parser.add_argument('name', help="Test for which to print results")
|
||||
results_parser.add_argument(
|
||||
'names', nargs=argparse.REMAINDER,
|
||||
help="Test suites for which to print results")
|
||||
|
||||
# Remove
|
||||
remove_parser = sp.add_parser('remove', help=test_remove.__doc__)
|
||||
arguments.add_common_arguments(remove_parser, ['yes_to_all'])
|
||||
remove_parser.add_argument(
|
||||
'name', nargs='?',
|
||||
help="Test to remove from test stage")
|
||||
'names', nargs=argparse.REMAINDER,
|
||||
help="Test suites to remove from test stage")
|
||||
|
||||
|
||||
def test_run(args):
|
||||
@@ -139,7 +148,7 @@ def test_run(args):
|
||||
specs_to_test.extend(matching)
|
||||
|
||||
# test_stage_dir
|
||||
test_suite = spack.install_test.TestSuite(specs_to_test, args.name)
|
||||
test_suite = spack.install_test.TestSuite(specs_to_test, args.alias)
|
||||
test_suite.ensure_stage()
|
||||
tty.msg("Spack test %s" % test_suite.name)
|
||||
|
||||
@@ -171,9 +180,16 @@ def test_run(args):
|
||||
|
||||
|
||||
def test_list(args):
|
||||
"""List tests that are running or have available results."""
|
||||
stage_dir = spack.install_test.get_test_stage_dir()
|
||||
tests = os.listdir(stage_dir)
|
||||
"""List all installed packages with available tests."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def test_find(args): # TODO: merge with status (noargs)
|
||||
"""Find tests that are running or have available results.
|
||||
|
||||
Displays aliases for tests that have them, otherwise test suite content
|
||||
hashes."""
|
||||
test_suites = spack.install_test.get_all_test_suites()
|
||||
|
||||
# Filter tests by filter argument
|
||||
if args.filter:
|
||||
@@ -185,14 +201,16 @@ def create_filter(f):
|
||||
|
||||
def match(t, f):
|
||||
return f.match(t)
|
||||
tests = [t for t in tests
|
||||
if any(match(t, f) for f in filters) and
|
||||
os.path.isdir(os.path.join(stage_dir, t))]
|
||||
test_suites = [t for t in test_suites
|
||||
if any(match(t.alias, f) for f in filters) and
|
||||
os.path.isdir(os.path.join(stage_dir, t))]
|
||||
|
||||
if tests:
|
||||
names = [t.name for t in test_suites]
|
||||
|
||||
if names:
|
||||
# TODO: Make these specify results vs active
|
||||
msg = "Spack test results available for the following tests:\n"
|
||||
msg += " %s\n" % ' '.join(tests)
|
||||
msg += " %s\n" % ' '.join(names)
|
||||
msg += " Run `spack test remove` to remove all tests"
|
||||
tty.msg(msg)
|
||||
else:
|
||||
@@ -202,40 +220,57 @@ def match(t, f):
|
||||
|
||||
|
||||
def test_status(args):
|
||||
"""Get the current status for a particular Spack test."""
|
||||
name = args.name
|
||||
stage = spack.install_test.get_test_stage(name)
|
||||
|
||||
if os.path.exists(stage):
|
||||
# TODO: Make this handle capability tests too
|
||||
tty.msg("Test %s completed" % name)
|
||||
"""Get the current status for a particular Spack test suites."""
|
||||
if args.names:
|
||||
test_suites = []
|
||||
for name in args.names:
|
||||
test_suite = spack.install_test.get_test_suite(name)
|
||||
if test_suite:
|
||||
test_suites.append(test_suite)
|
||||
else:
|
||||
tty.msg("No test suite %s found in test stage" % name)
|
||||
else:
|
||||
tty.msg("Test %s no longer available" % name)
|
||||
test_suites = spack.install_test.get_all_test_suites()
|
||||
if not test_suites:
|
||||
tty.msg("No test suites with status to report")
|
||||
|
||||
for test_suite in test_suites:
|
||||
# TODO: Make this handle capability tests too
|
||||
# TODO: Make this handle tests running in another process
|
||||
tty.msg("Test suite %s completed" % test_suite.name)
|
||||
|
||||
|
||||
def test_results(args):
|
||||
"""Get the results for a particular Spack test."""
|
||||
name = args.name
|
||||
stage = spack.install_test.get_test_stage(name)
|
||||
"""Get the results from Spack test suites (default all)."""
|
||||
if args.names:
|
||||
test_suites = []
|
||||
for name in args.names:
|
||||
test_suite = spack.install_test.get_test_suite(name)
|
||||
if test_suite:
|
||||
test_suites.append(test_suite)
|
||||
else:
|
||||
tty.msg("No test suite %s found in test stage" % name)
|
||||
else:
|
||||
test_suites = spack.install_test.get_all_test_suites()
|
||||
if not test_suites:
|
||||
tty.msg("No test suites with results to report")
|
||||
|
||||
# TODO: Make this handle capability tests too
|
||||
# The results file may turn out to be a placeholder for future work
|
||||
if os.path.exists(stage):
|
||||
results_file = spack.install_test.get_results_file(name)
|
||||
for test_suite in test_suites:
|
||||
results_file = test_suite.results_file
|
||||
if os.path.exists(results_file):
|
||||
msg = "Results for test %s: \n" % name
|
||||
msg = "Results for test suite %s: \n" % test_suite.name
|
||||
with open(results_file, 'r') as f:
|
||||
lines = f.readlines()
|
||||
for line in lines:
|
||||
msg += " %s" % line
|
||||
tty.msg(msg)
|
||||
else:
|
||||
msg = "Test %s has no results.\n" % name
|
||||
msg += " Check if it is active with "
|
||||
msg += "`spack test status %s`" % name
|
||||
msg = "Test %s has no results.\n" % test_suite.name
|
||||
msg += " Check if it is running with "
|
||||
msg += "`spack test status %s`" % test_suite.name
|
||||
tty.msg(msg)
|
||||
else:
|
||||
tty.msg("No test %s found in test stage" % name)
|
||||
|
||||
|
||||
def test_remove(args):
|
||||
@@ -245,11 +280,32 @@ def test_remove(args):
|
||||
|
||||
Removed tests can no longer be accessed for results or status, and will not
|
||||
appear in `spack test list` results."""
|
||||
if args.name:
|
||||
shutil.rmtree(spack.install_test.get_test_stage(args.name))
|
||||
if args.names:
|
||||
test_suites = []
|
||||
for name in args.names:
|
||||
test_suite = spack.install_test.get_test_suite(name)
|
||||
if test_suite:
|
||||
test_suites.append(test_suite)
|
||||
else:
|
||||
tty.msg("No test suite %s found in test stage" % name)
|
||||
else:
|
||||
fs.remove_directory_contents(spack.install_test.get_test_stage_dir())
|
||||
test_suites = spack.install_test.get_all_test_suites()
|
||||
|
||||
if not test_suites:
|
||||
tty.msg("No test suites to remove")
|
||||
return
|
||||
|
||||
if not args.yes_to_all:
|
||||
msg = 'The following test suites will be removed'
|
||||
msg += '\n\n ' + ' '.join(test.name for test in test_suites) + '\n'
|
||||
tty.msg(msg)
|
||||
answer = tty.get_yes_or_no('Do you want to proceed?', default=False)
|
||||
if not answer:
|
||||
tty.msg('Aborting removal of test suites')
|
||||
return
|
||||
|
||||
for test_suite in test_suites:
|
||||
shutil.rmtree(test_suite.stage)
|
||||
|
||||
def test(parser, args):
|
||||
globals()['test_%s' % args.test_command](args)
|
||||
|
@@ -18,6 +18,10 @@
|
||||
import spack.util.spack_json as sjson
|
||||
|
||||
|
||||
test_suite_filename = 'test_suite.lock'
|
||||
results_filename = 'results.txt'
|
||||
|
||||
|
||||
def get_escaped_text_output(filename):
|
||||
"""Retrieve and escape the expected text output from the file
|
||||
|
||||
@@ -41,43 +45,61 @@ def get_test_stage_dir():
|
||||
spack.config.get('config:test_stage', '~/.spack/test'))
|
||||
|
||||
|
||||
def get_test_stage(name):
|
||||
return spack.util.prefix.Prefix(os.path.join(get_test_stage_dir(), name))
|
||||
def get_all_test_suites():
|
||||
stage_root = get_test_stage_dir()
|
||||
|
||||
def valid_stage(d):
|
||||
dirpath = os.path.join(stage_root, d)
|
||||
return (os.path.isdir(dirpath) and
|
||||
test_suite_filename in os.listdir(dirpath))
|
||||
|
||||
candidates = [
|
||||
os.path.join(stage_root, d, test_suite_filename)
|
||||
for d in os.listdir(stage_root)
|
||||
if valid_stage(d)
|
||||
]
|
||||
|
||||
test_suites = [TestSuite.from_file(c) for c in candidates]
|
||||
return test_suites
|
||||
|
||||
|
||||
def get_results_file(name):
|
||||
return get_test_stage(name).join('results.txt')
|
||||
def get_test_suite(name):
|
||||
assert name, "Cannot search for empty test name or 'None'"
|
||||
test_suites = get_all_test_suites()
|
||||
names = [ts for ts in test_suites
|
||||
if ts.name == name]
|
||||
assert len(names) < 2, "alias shadows test suite hash"
|
||||
|
||||
|
||||
def get_test_by_name(name):
|
||||
test_suite_file = get_test_stage(name).join('specs.lock')
|
||||
|
||||
if not os.path.isdir(test_suite_file):
|
||||
raise Exception
|
||||
|
||||
test_suite_data = sjson.load(test_suite_file)
|
||||
return TestSuite.from_dict(test_suite_data, name)
|
||||
if not names:
|
||||
return None
|
||||
return names[0]
|
||||
|
||||
|
||||
class TestSuite(object):
|
||||
def __init__(self, specs, name=None):
|
||||
self._name = name
|
||||
def __init__(self, specs, alias=None):
|
||||
# copy so that different test suites have different package objects
|
||||
# even if they contain the same spec
|
||||
self.specs = [spec.copy() for spec in specs]
|
||||
self.current_test_spec = None # spec currently tested, can be virtual
|
||||
self.current_base_spec = None # spec currently running do_test
|
||||
|
||||
self.alias = alias
|
||||
self._hash = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if not self._name:
|
||||
return self.alias if self.alias else self.content_hash
|
||||
|
||||
@property
|
||||
def content_hash(self):
|
||||
if not self._hash:
|
||||
json_text = sjson.dump(self.to_dict())
|
||||
sha = hashlib.sha1(json_text.encode('utf-8'))
|
||||
b32_hash = base64.b32encode(sha.digest()).lower()
|
||||
if sys.version_info[0] >= 3:
|
||||
b32_hash = b32_hash.decode('utf-8')
|
||||
self._name = b32_hash
|
||||
return self._name
|
||||
self._hash = b32_hash
|
||||
return self._hash
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
self.write_reproducibility_data()
|
||||
@@ -104,7 +126,6 @@ def __call__(self, *args, **kwargs):
|
||||
|
||||
# run the package tests
|
||||
spec.package.do_test(
|
||||
name=self.name,
|
||||
dirty=dirty
|
||||
)
|
||||
|
||||
@@ -134,11 +155,12 @@ def ensure_stage(self):
|
||||
|
||||
@property
|
||||
def stage(self):
|
||||
return get_test_stage(self.name)
|
||||
return spack.util.prefix.Prefix(
|
||||
os.path.join(get_test_stage_dir(), self.content_hash))
|
||||
|
||||
@property
|
||||
def results_file(self):
|
||||
return get_results_file(self.name)
|
||||
return self.stage.join(results_filename)
|
||||
|
||||
@classmethod
|
||||
def test_pkg_id(cls, spec):
|
||||
@@ -191,17 +213,31 @@ def write_reproducibility_data(self):
|
||||
except spack.repo.UnknownPackageError:
|
||||
pass # not all virtuals have package files
|
||||
|
||||
with open(self.stage.join('specs.lock'), 'w') as f:
|
||||
with open(self.stage.join(test_suite_filename), 'w') as f:
|
||||
sjson.dump(self.to_dict(), stream=f)
|
||||
|
||||
def to_dict(self):
|
||||
specs = [s.to_dict() for s in self.specs]
|
||||
return {'specs': specs}
|
||||
d = {'specs': specs}
|
||||
if self.alias:
|
||||
d['alias'] = self.alias
|
||||
return d
|
||||
|
||||
@staticmethod
|
||||
def from_dict(d, name=None):
|
||||
def from_dict(d):
|
||||
specs = [Spec.from_dict(spec_dict) for spec_dict in d['specs']]
|
||||
return TestSuite(specs, name)
|
||||
alias = d.get('alias', None)
|
||||
return TestSuite(specs, alias)
|
||||
|
||||
@staticmethod
|
||||
def from_file(filename):
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
data = sjson.load(f)
|
||||
return TestSuite.from_dict(data)
|
||||
except Exception as e:
|
||||
tty.debug(e)
|
||||
raise sjson.SpackJSONError("error parsing JSON TestSuite:", str(e))
|
||||
|
||||
|
||||
def _add_msg_to_file(filename, msg):
|
||||
|
@@ -1659,7 +1659,7 @@ def cache_extra_test_sources(self, srcs):
|
||||
test_failures = None
|
||||
test_suite = None
|
||||
|
||||
def do_test(self, name, remove_directory=False, dirty=False):
|
||||
def do_test(self, dirty=False):
|
||||
if self.test_requires_compiler:
|
||||
compilers = spack.compilers.compilers_for_spec(
|
||||
self.spec.compiler, arch_spec=self.spec.architecture)
|
||||
@@ -1733,17 +1733,12 @@ def test_process():
|
||||
if self.test_failures:
|
||||
raise TestFailure(self.test_failures)
|
||||
|
||||
# cleanup test directory on success
|
||||
if remove_directory:
|
||||
shutil.rmtree(testdir)
|
||||
|
||||
finally:
|
||||
# reset debug level
|
||||
tty.set_debug(old_debug)
|
||||
|
||||
spack.build_environment.fork(
|
||||
self, test_process, dirty=dirty, fake=False, context='test',
|
||||
test_name=name)
|
||||
self, test_process, dirty=dirty, fake=False, context='test')
|
||||
|
||||
def test(self):
|
||||
pass
|
||||
|
@@ -33,7 +33,6 @@ def __call__(self, *args, **kwargs):
|
||||
spack.caches.misc_cache, 'destroy', Counter('caches'))
|
||||
monkeypatch.setattr(
|
||||
spack.installer, 'clear_failures', Counter('failures'))
|
||||
monkeypatch.setattr(fs, 'remove_directory_contents', Counter('tests'))
|
||||
|
||||
yield counts
|
||||
|
||||
@@ -50,7 +49,6 @@ def __call__(self, *args, **kwargs):
|
||||
('-sd', ['stages', 'downloads']),
|
||||
('-m', ['caches']),
|
||||
('-f', ['failures']),
|
||||
('-t', ['tests']),
|
||||
('-a', all_effects),
|
||||
('', []),
|
||||
])
|
||||
|
Reference in New Issue
Block a user