Updates to stand-alone test documentation (#33703)

This commit is contained in:
Tamara Dahlgren
2022-11-04 11:55:38 -07:00
committed by GitHub
parent 33e5e77225
commit d338ac0634
2 changed files with 154 additions and 22 deletions

View File

@@ -43,12 +43,24 @@ def get_escaped_text_output(filename):
def get_test_stage_dir():
"""Retrieves the ``config:test_stage`` path to the configured test stage
root directory
Returns:
str: absolute path to the configured test stage root or, if none,
the default test stage path
"""
return spack.util.path.canonicalize_path(
spack.config.get("config:test_stage", spack.paths.default_test_path)
)
def get_all_test_suites():
"""Retrieves all validly staged TestSuites
Returns:
list: a list of TestSuite objects, which may be empty if there are none
"""
stage_root = get_test_stage_dir()
if not os.path.isdir(stage_root):
return []
@@ -68,7 +80,14 @@ def valid_stage(d):
def get_named_test_suites(name):
"""Return a list of the names of any test suites with that name."""
"""Retrieves test suites with the provided name.
Returns:
list: a list of matching TestSuite instances, which may be empty if none
Raises:
TestSuiteNameError: If no name is provided
"""
if not name:
raise TestSuiteNameError("Test suite name is required.")
@@ -77,6 +96,14 @@ def get_named_test_suites(name):
def get_test_suite(name):
"""Ensure there is only one matching test suite with the provided name.
Returns:
str or None: the name if one matching test suite, else None
Raises:
TestSuiteNameError: If there is more than one matching TestSuite
"""
names = get_named_test_suites(name)
if len(names) > 1:
raise TestSuiteNameError('Too many suites named "{0}". May shadow hash.'.format(name))
@@ -87,12 +114,14 @@ def get_test_suite(name):
def write_test_suite_file(suite):
"""Write the test suite to its lock file."""
"""Write the test suite to its (JSON) lock file."""
with open(suite.stage.join(test_suite_filename), "w") as f:
sjson.dump(suite.to_dict(), stream=f)
def write_test_summary(num_failed, num_skipped, num_untested, num_specs):
"""Write a well formatted summary of the totals for each relevant status
category."""
failed = "{0} failed, ".format(num_failed) if num_failed else ""
skipped = "{0} skipped, ".format(num_skipped) if num_skipped else ""
no_tests = "{0} no-tests, ".format(num_untested) if num_untested else ""
@@ -108,6 +137,8 @@ def write_test_summary(num_failed, num_skipped, num_untested, num_specs):
class TestSuite(object):
"""The class that manages specs for ``spack test run`` execution."""
def __init__(self, specs, alias=None):
# copy so that different test suites have different package objects
# even if they contain the same spec
@@ -122,10 +153,12 @@ def __init__(self, specs, alias=None):
@property
def name(self):
"""The name (alias or, if none, hash) of the test suite."""
return self.alias if self.alias else self.content_hash
@property
def content_hash(self):
"""The hash used to uniquely identify the test suite."""
if not self._hash:
json_text = sjson.dump(self.to_dict())
sha = hashlib.sha1(json_text.encode("utf-8"))
@@ -212,48 +245,100 @@ def __call__(self, *args, **kwargs):
raise TestSuiteFailure(self.fails)
def ensure_stage(self):
"""Ensure the test suite stage directory exists."""
if not os.path.exists(self.stage):
fs.mkdirp(self.stage)
@property
def stage(self):
"""The root test suite stage directory."""
return spack.util.prefix.Prefix(os.path.join(get_test_stage_dir(), self.content_hash))
@property
def results_file(self):
"""The path to the results summary file."""
return self.stage.join(results_filename)
@classmethod
def test_pkg_id(cls, spec):
"""Build the standard install test package identifier
"""The standard install test package identifier.
Args:
spec (Spec): instance of the spec under test
spec (spack.spec.Spec): instance of the spec under test
Returns:
(str): the install test package identifier
str: the install test package identifier
"""
return spec.format("{name}-{version}-{hash:7}")
@classmethod
def test_log_name(cls, spec):
"""The standard log filename for a spec.
Args:
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the spec's log filename
"""
return "%s-test-out.txt" % cls.test_pkg_id(spec)
def log_file_for_spec(self, spec):
"""The test log file path for the provided spec.
Args:
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the path to the spec's log file
"""
return self.stage.join(self.test_log_name(spec))
def test_dir_for_spec(self, spec):
"""The path to the test stage directory for the provided spec.
Args:
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the spec's test stage directory path
"""
return self.stage.join(self.test_pkg_id(spec))
@classmethod
def tested_file_name(cls, spec):
"""The standard test status filename for the spec.
Args:
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the spec's test status filename
"""
return "%s-tested.txt" % cls.test_pkg_id(spec)
def tested_file_for_spec(self, spec):
"""The test status file path for the spec.
Args:
spec (spack.spec.Spec): instance of the spec under test
Returns:
str: the spec's test status file path
"""
return self.stage.join(self.tested_file_name(spec))
@property
def current_test_cache_dir(self):
"""Path to the test stage directory where the current spec's cached
build-time files were automatically copied.
Returns:
str: path to the current spec's staged, cached build-time files.
Raises:
TestSuiteSpecError: If there is no spec being tested
"""
if not (self.current_test_spec and self.current_base_spec):
raise TestSuiteSpecError("Unknown test cache directory: no specs being tested")
@@ -263,6 +348,15 @@ def current_test_cache_dir(self):
@property
def current_test_data_dir(self):
"""Path to the test stage directory where the current spec's custom
package (data) files were automatically copied.
Returns:
str: path to the current spec's staged, custom package (data) files
Raises:
TestSuiteSpecError: If there is no spec being tested
"""
if not (self.current_test_spec and self.current_base_spec):
raise TestSuiteSpecError("Unknown test data directory: no specs being tested")
@@ -270,13 +364,13 @@ def current_test_data_dir(self):
base_spec = self.current_base_spec
return self.test_dir_for_spec(base_spec).data.join(test_spec.name)
def add_failure(self, exc, msg):
current_hash = self.current_base_spec.dag_hash()
current_failures = self.failures.get(current_hash, [])
current_failures.append((exc, msg))
self.failures[current_hash] = current_failures
def write_test_result(self, spec, result):
"""Write the spec's test result to the test suite results file.
Args:
spec (spack.spec.Spec): instance of the spec under test
result (str): result from the spec's test execution (e.g, PASSED)
"""
msg = "{0} {1}".format(self.test_pkg_id(spec), result)
_add_msg_to_file(self.results_file, msg)
@@ -295,6 +389,14 @@ def write_reproducibility_data(self):
write_test_suite_file(self)
def to_dict(self):
"""Build a dictionary for the test suite.
Returns:
dict: The dictionary contains entries for up to two keys:
specs: list of the test suite's specs in dictionary form
alias: the alias, or name, given to the test suite if provided
"""
specs = [s.to_dict() for s in self.specs]
d = {"specs": specs}
if self.alias:
@@ -303,12 +405,29 @@ def to_dict(self):
@staticmethod
def from_dict(d):
"""Instantiates a TestSuite based on a dictionary specs and an
optional alias:
specs: list of the test suite's specs in dictionary form
alias: the test suite alias
Returns:
TestSuite: Instance of TestSuite created from the specs
"""
specs = [Spec.from_dict(spec_dict) for spec_dict in d["specs"]]
alias = d.get("alias", None)
return TestSuite(specs, alias)
@staticmethod
def from_file(filename):
"""Instantiate a TestSuite using the specs and optional alias
provided in the given file.
Args:
filename (str): The path to the JSON file containing the test
suite specs and optional alias.
"""
try:
with open(filename, "r") as f:
data = sjson.load(f)
@@ -324,7 +443,7 @@ def from_file(filename):
def _add_msg_to_file(filename, msg):
"""Add the message to the specified file
"""Append the message to the specified file.
Args:
filename (str): path to the file