Stand-alone testing: remove deprecated methods (#45752)
* Stand-alone testing: remove deprecated methods * audit: replace deprecated test method checks for test callbacks (AcriusWinter) * install_test: replace deprecated with new test method name starts (AcriusWinter) * package_base: removed deprecated test methods (AcriusWinter) * test/package_class: remove deprecated test methods (AcriusWinter) * test/reporters: remove deprecated test methods (AcriusWinter) * reporters/extract: remove deprecated test-related methods (AcriusWinter) * py-test-callback: rename test in callbacks and output (AcriusWinter) * reporters/extract: remove deprecated expected failure output capture (AcriusWinter) * stand-alone test cleanup: f-string, remove deprecation warning, etc. (AcriusWinter) * stand-alone tests: f-string fix and remove unused imports (AcriusWinter) * package_base: finish removing the rest of deprecated run_test method (AcriusWinter) * py-test-callback: remove stand-alone test method (AcriusWinter) * package_base: remove now unused imports (AcriusWinter) * Python: test_imports replaces test (tldahlgren) * mptensor, trivial-smoke-test: replace deprecated cache_extra_test_sources method (tldahlgren) * trivial-smoke-test: replace deprecated install_test_root method (tldahlgren) * docs: update perl and scons package's test methods (tldahlgren) * builder: remove deprecated test() method (tldahlgren) * Update legion and mfem test data for stand-alone tests (tldahlgren) * py-test-callback: restore the test method * Audit/stand-alone testing: Fix and added stand-alone test audit checks - fix audit failure message for build-time test callback check - remove empty test method check during stand-alone testing - change build-time test callback check to package_properties - add test method docstring audit check and mock fail-test-audit-docstring pkg - add test method implementation audit check and mock fail-test-audit-impl pkg - add new mock packages to test_package_audits and test_test_list_all * audits: loosen docstring content constraints * Add missing test method docstring * py-test-callback: resolve builder issues * Audits: Add checks for use of deprecated stand-alone test methods; improve output for other test-related checks * Fix style issues * test_test_list_all: add new fail-test-audit-deprecated package --------- Signed-off-by: Tamara Dahlgren <dahlgren1@llnl.gov> Co-authored-by: Tamara Dahlgren <35777542+tldahlgren@users.noreply.github.com>
This commit is contained in:
parent
feabcb884a
commit
76243bfcd7
@ -130,14 +130,19 @@ before or after a particular phase. For example, in ``perl``, we see:
|
|||||||
|
|
||||||
@run_after("install")
|
@run_after("install")
|
||||||
def install_cpanm(self):
|
def install_cpanm(self):
|
||||||
spec = self.spec
|
spec = self.spec
|
||||||
|
maker = make
|
||||||
if spec.satisfies("+cpanm"):
|
cpan_dir = join_path("cpanm", "cpanm")
|
||||||
with working_dir(join_path("cpanm", "cpanm")):
|
if sys.platform == "win32":
|
||||||
perl = spec["perl"].command
|
maker = nmake
|
||||||
perl("Makefile.PL")
|
cpan_dir = join_path(self.stage.source_path, cpan_dir)
|
||||||
make()
|
cpan_dir = windows_sfn(cpan_dir)
|
||||||
make("install")
|
if "+cpanm" in spec:
|
||||||
|
with working_dir(cpan_dir):
|
||||||
|
perl = spec["perl"].command
|
||||||
|
perl("Makefile.PL")
|
||||||
|
maker()
|
||||||
|
maker("install")
|
||||||
|
|
||||||
This extra step automatically installs ``cpanm`` in addition to the
|
This extra step automatically installs ``cpanm`` in addition to the
|
||||||
base Perl installation.
|
base Perl installation.
|
||||||
@ -176,8 +181,14 @@ In the ``perl`` package, we can see:
|
|||||||
|
|
||||||
@run_after("build")
|
@run_after("build")
|
||||||
@on_package_attributes(run_tests=True)
|
@on_package_attributes(run_tests=True)
|
||||||
def test(self):
|
def build_test(self):
|
||||||
make("test")
|
if sys.platform == "win32":
|
||||||
|
win32_dir = os.path.join(self.stage.source_path, "win32")
|
||||||
|
win32_dir = windows_sfn(win32_dir)
|
||||||
|
with working_dir(win32_dir):
|
||||||
|
nmake("test", ignore_quotes=True)
|
||||||
|
else:
|
||||||
|
make("test")
|
||||||
|
|
||||||
As you can guess, this runs ``make test`` *after* building the package,
|
As you can guess, this runs ``make test`` *after* building the package,
|
||||||
if and only if testing is requested. Again, this is not specific to
|
if and only if testing is requested. Again, this is not specific to
|
||||||
|
@ -49,14 +49,14 @@ following phases:
|
|||||||
#. ``install`` - install the package
|
#. ``install`` - install the package
|
||||||
|
|
||||||
Package developers often add unit tests that can be invoked with
|
Package developers often add unit tests that can be invoked with
|
||||||
``scons test`` or ``scons check``. Spack provides a ``test`` method
|
``scons test`` or ``scons check``. Spack provides a ``build_test`` method
|
||||||
to handle this. Since we don't know which one the package developer
|
to handle this. Since we don't know which one the package developer
|
||||||
chose, the ``test`` method does nothing by default, but can be easily
|
chose, the ``build_test`` method does nothing by default, but can be easily
|
||||||
overridden like so:
|
overridden like so:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def test(self):
|
def build_test(self):
|
||||||
scons("check")
|
scons("check")
|
||||||
|
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ def _search_duplicate_compilers(error_cls):
|
|||||||
import collections
|
import collections
|
||||||
import collections.abc
|
import collections.abc
|
||||||
import glob
|
import glob
|
||||||
|
import inspect
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
import os
|
||||||
@ -50,6 +51,7 @@ def _search_duplicate_compilers(error_cls):
|
|||||||
from urllib.request import urlopen
|
from urllib.request import urlopen
|
||||||
|
|
||||||
import llnl.util.lang
|
import llnl.util.lang
|
||||||
|
from llnl.string import plural
|
||||||
|
|
||||||
import spack.builder
|
import spack.builder
|
||||||
import spack.config
|
import spack.config
|
||||||
@ -386,6 +388,14 @@ def _make_config_error(config_data, summary, error_cls):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
package_deprecated_attributes = AuditClass(
|
||||||
|
group="packages",
|
||||||
|
tag="PKG-DEPRECATED-ATTRIBUTES",
|
||||||
|
description="Sanity checks to preclude use of deprecated package attributes",
|
||||||
|
kwargs=("pkgs",),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
package_properties = AuditClass(
|
package_properties = AuditClass(
|
||||||
group="packages",
|
group="packages",
|
||||||
tag="PKG-PROPERTIES",
|
tag="PKG-PROPERTIES",
|
||||||
@ -404,22 +414,23 @@ def _make_config_error(config_data, summary, error_cls):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@package_directives
|
@package_properties
|
||||||
def _check_build_test_callbacks(pkgs, error_cls):
|
def _check_build_test_callbacks(pkgs, error_cls):
|
||||||
"""Ensure stand-alone test method is not included in build-time callbacks"""
|
"""Ensure stand-alone test methods are not included in build-time callbacks.
|
||||||
|
|
||||||
|
Test methods are for checking the installed software as stand-alone tests.
|
||||||
|
They could also be called during the post-install phase of a build.
|
||||||
|
"""
|
||||||
errors = []
|
errors = []
|
||||||
for pkg_name in pkgs:
|
for pkg_name in pkgs:
|
||||||
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||||
test_callbacks = getattr(pkg_cls, "build_time_test_callbacks", None)
|
test_callbacks = getattr(pkg_cls, "build_time_test_callbacks", None)
|
||||||
|
|
||||||
# TODO (post-34236): "test*"->"test_*" once remove deprecated methods
|
has_test_method = test_callbacks and any([m.startswith("test_") for m in test_callbacks])
|
||||||
# TODO (post-34236): "test"->"test_" once remove deprecated methods
|
|
||||||
has_test_method = test_callbacks and any([m.startswith("test") for m in test_callbacks])
|
|
||||||
if has_test_method:
|
if has_test_method:
|
||||||
msg = '{0} package contains "test*" method(s) in ' "build_time_test_callbacks"
|
msg = f"Package {pkg_name} includes stand-alone test methods in build-time checks."
|
||||||
instr = 'Remove all methods whose names start with "test" from: [{0}]'.format(
|
callbacks = ", ".join(test_callbacks)
|
||||||
", ".join(test_callbacks)
|
instr = f"Remove the following from 'build_time_test_callbacks': {callbacks}"
|
||||||
)
|
|
||||||
errors.append(error_cls(msg.format(pkg_name), [instr]))
|
errors.append(error_cls(msg.format(pkg_name), [instr]))
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
@ -517,6 +528,46 @@ def _search_for_reserved_attributes_names_in_packages(pkgs, error_cls):
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@package_deprecated_attributes
|
||||||
|
def _search_for_deprecated_package_methods(pkgs, error_cls):
|
||||||
|
"""Ensure the package doesn't define or use deprecated methods"""
|
||||||
|
DEPRECATED_METHOD = (("test", "a name starting with 'test_'"),)
|
||||||
|
DEPRECATED_USE = (
|
||||||
|
("self.cache_extra_test_sources(", "cache_extra_test_sources(self, ..)"),
|
||||||
|
("self.install_test_root(", "install_test_root(self, ..)"),
|
||||||
|
("self.run_test(", "test_part(self, ..)"),
|
||||||
|
)
|
||||||
|
errors = []
|
||||||
|
for pkg_name in pkgs:
|
||||||
|
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||||
|
methods = inspect.getmembers(pkg_cls, predicate=lambda x: inspect.isfunction(x))
|
||||||
|
method_errors = collections.defaultdict(list)
|
||||||
|
for name, function in methods:
|
||||||
|
for deprecated_name, alternate in DEPRECATED_METHOD:
|
||||||
|
if name == deprecated_name:
|
||||||
|
msg = f"Rename '{deprecated_name}' method to {alternate} instead."
|
||||||
|
method_errors[name].append(msg)
|
||||||
|
|
||||||
|
source = inspect.getsource(function)
|
||||||
|
for deprecated_name, alternate in DEPRECATED_USE:
|
||||||
|
if deprecated_name in source:
|
||||||
|
msg = f"Change '{deprecated_name}' to '{alternate}' in '{name}' method."
|
||||||
|
method_errors[name].append(msg)
|
||||||
|
|
||||||
|
num_methods = len(method_errors)
|
||||||
|
if num_methods > 0:
|
||||||
|
methods = plural(num_methods, "method", show_n=False)
|
||||||
|
error_msg = (
|
||||||
|
f"Package '{pkg_name}' implements or uses unsupported deprecated {methods}."
|
||||||
|
)
|
||||||
|
instr = [f"Make changes to '{pkg_cls.__module__}':"]
|
||||||
|
for name in sorted(method_errors):
|
||||||
|
instr.extend([f" {msg}" for msg in method_errors[name]])
|
||||||
|
errors.append(error_cls(error_msg, instr))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
@package_properties
|
@package_properties
|
||||||
def _ensure_all_package_names_are_lowercase(pkgs, error_cls):
|
def _ensure_all_package_names_are_lowercase(pkgs, error_cls):
|
||||||
"""Ensure package names are lowercase and consistent"""
|
"""Ensure package names are lowercase and consistent"""
|
||||||
@ -771,6 +822,89 @@ def _uses_deprecated_globals(pkgs, error_cls):
|
|||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@package_properties
|
||||||
|
def _ensure_test_docstring(pkgs, error_cls):
|
||||||
|
"""Ensure stand-alone test methods have a docstring.
|
||||||
|
|
||||||
|
The docstring of a test method is implicitly used as the description of
|
||||||
|
the corresponding test part during test results reporting.
|
||||||
|
"""
|
||||||
|
doc_regex = r'\s+("""[^"]+""")'
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
for pkg_name in pkgs:
|
||||||
|
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||||
|
methods = inspect.getmembers(pkg_cls, predicate=lambda x: inspect.isfunction(x))
|
||||||
|
method_names = []
|
||||||
|
for name, test_fn in methods:
|
||||||
|
if not name.startswith("test_"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Ensure the test method has a docstring
|
||||||
|
source = inspect.getsource(test_fn)
|
||||||
|
match = re.search(doc_regex, source)
|
||||||
|
if match is None or len(match.group(0).replace('"', "").strip()) == 0:
|
||||||
|
method_names.append(name)
|
||||||
|
|
||||||
|
num_methods = len(method_names)
|
||||||
|
if num_methods > 0:
|
||||||
|
methods = plural(num_methods, "method", show_n=False)
|
||||||
|
docstrings = plural(num_methods, "docstring", show_n=False)
|
||||||
|
msg = f"Package {pkg_name} has test {methods} with empty or missing {docstrings}."
|
||||||
|
names = ", ".join(method_names)
|
||||||
|
instr = [
|
||||||
|
"Docstrings are used as descriptions in test outputs.",
|
||||||
|
f"Add a concise summary to the following {methods} in '{pkg_cls.__module__}':",
|
||||||
|
f"{names}",
|
||||||
|
]
|
||||||
|
errors.append(error_cls(msg, instr))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
@package_properties
|
||||||
|
def _ensure_test_implemented(pkgs, error_cls):
|
||||||
|
"""Ensure stand-alone test methods are implemented.
|
||||||
|
|
||||||
|
The test method is also required to be non-empty.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def skip(line):
|
||||||
|
ln = line.strip()
|
||||||
|
return ln.startswith("#") or "pass" in ln
|
||||||
|
|
||||||
|
doc_regex = r'\s+("""[^"]+""")'
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
for pkg_name in pkgs:
|
||||||
|
pkg_cls = spack.repo.PATH.get_pkg_class(pkg_name)
|
||||||
|
methods = inspect.getmembers(pkg_cls, predicate=lambda x: inspect.isfunction(x))
|
||||||
|
method_names = []
|
||||||
|
for name, test_fn in methods:
|
||||||
|
if not name.startswith("test_"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
source = inspect.getsource(test_fn)
|
||||||
|
|
||||||
|
# Attempt to ensure the test method is implemented.
|
||||||
|
impl = re.sub(doc_regex, r"", source).splitlines()[1:]
|
||||||
|
lines = [ln.strip() for ln in impl if not skip(ln)]
|
||||||
|
if not lines:
|
||||||
|
method_names.append(name)
|
||||||
|
|
||||||
|
num_methods = len(method_names)
|
||||||
|
if num_methods > 0:
|
||||||
|
methods = plural(num_methods, "method", show_n=False)
|
||||||
|
msg = f"Package {pkg_name} has empty or missing test {methods}."
|
||||||
|
names = ", ".join(method_names)
|
||||||
|
instr = [
|
||||||
|
f"Implement or remove the following {methods} from '{pkg_cls.__module__}': {names}"
|
||||||
|
]
|
||||||
|
errors.append(error_cls(msg, instr))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
@package_https_directives
|
@package_https_directives
|
||||||
def _linting_package_file(pkgs, error_cls):
|
def _linting_package_file(pkgs, error_cls):
|
||||||
"""Check for correctness of links"""
|
"""Check for correctness of links"""
|
||||||
|
@ -339,7 +339,7 @@ class PythonPackage(PythonExtension):
|
|||||||
legacy_buildsystem = "python_pip"
|
legacy_buildsystem = "python_pip"
|
||||||
|
|
||||||
#: Callback names for install-time test
|
#: Callback names for install-time test
|
||||||
install_time_test_callbacks = ["test"]
|
install_time_test_callbacks = ["test_imports"]
|
||||||
|
|
||||||
build_system("python_pip")
|
build_system("python_pip")
|
||||||
|
|
||||||
@ -429,7 +429,7 @@ class PythonPipBuilder(BaseBuilder):
|
|||||||
phases = ("install",)
|
phases = ("install",)
|
||||||
|
|
||||||
#: Names associated with package methods in the old build-system format
|
#: Names associated with package methods in the old build-system format
|
||||||
legacy_methods = ("test",)
|
legacy_methods = ("test_imports",)
|
||||||
|
|
||||||
#: Same as legacy_methods, but the signature is different
|
#: Same as legacy_methods, but the signature is different
|
||||||
legacy_long_methods = ("install_options", "global_options", "config_settings")
|
legacy_long_methods = ("install_options", "global_options", "config_settings")
|
||||||
@ -438,7 +438,7 @@ class PythonPipBuilder(BaseBuilder):
|
|||||||
legacy_attributes = ("archive_files", "build_directory", "install_time_test_callbacks")
|
legacy_attributes = ("archive_files", "build_directory", "install_time_test_callbacks")
|
||||||
|
|
||||||
#: Callback names for install-time test
|
#: Callback names for install-time test
|
||||||
install_time_test_callbacks = ["test"]
|
install_time_test_callbacks = ["test_imports"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def std_args(cls) -> List[str]:
|
def std_args(cls) -> List[str]:
|
||||||
|
@ -521,10 +521,6 @@ def stage(self):
|
|||||||
def prefix(self):
|
def prefix(self):
|
||||||
return self.pkg.prefix
|
return self.pkg.prefix
|
||||||
|
|
||||||
def test(self):
|
|
||||||
# Defer tests to virtual and concrete packages
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setup_build_environment(self, env):
|
def setup_build_environment(self, env):
|
||||||
"""Sets up the build environment for a package.
|
"""Sets up the build environment for a package.
|
||||||
|
|
||||||
|
@ -372,8 +372,7 @@ def phase_tests(
|
|||||||
builder.pkg.test_suite.current_test_spec = builder.pkg.spec
|
builder.pkg.test_suite.current_test_spec = builder.pkg.spec
|
||||||
builder.pkg.test_suite.current_base_spec = builder.pkg.spec
|
builder.pkg.test_suite.current_base_spec = builder.pkg.spec
|
||||||
|
|
||||||
# TODO (post-34236): "test"->"test_" once remove deprecated methods
|
have_tests = any(name.startswith("test_") for name in method_names)
|
||||||
have_tests = any(name.startswith("test") for name in method_names)
|
|
||||||
if have_tests:
|
if have_tests:
|
||||||
copy_test_files(builder.pkg, builder.pkg.spec)
|
copy_test_files(builder.pkg, builder.pkg.spec)
|
||||||
|
|
||||||
@ -477,16 +476,9 @@ def write_tested_status(self):
|
|||||||
def test_part(pkg: Pb, test_name: str, purpose: str, work_dir: str = ".", verbose: bool = False):
|
def test_part(pkg: Pb, test_name: str, purpose: str, work_dir: str = ".", verbose: bool = False):
|
||||||
wdir = "." if work_dir is None else work_dir
|
wdir = "." if work_dir is None else work_dir
|
||||||
tester = pkg.tester
|
tester = pkg.tester
|
||||||
# TODO (post-34236): "test"->"test_" once remove deprecated methods
|
|
||||||
assert test_name and test_name.startswith(
|
assert test_name and test_name.startswith(
|
||||||
"test"
|
"test_"
|
||||||
), f"Test name must start with 'test' but {test_name} was provided"
|
), f"Test name must start with 'test_' but {test_name} was provided"
|
||||||
|
|
||||||
if test_name == "test":
|
|
||||||
tty.warn(
|
|
||||||
"{}: the 'test' method is deprecated. Convert stand-alone "
|
|
||||||
"test(s) to methods with names starting 'test_'.".format(pkg.name)
|
|
||||||
)
|
|
||||||
|
|
||||||
title = "test: {}: {}".format(test_name, purpose or "unspecified purpose")
|
title = "test: {}: {}".format(test_name, purpose or "unspecified purpose")
|
||||||
with fs.working_dir(wdir, create=True):
|
with fs.working_dir(wdir, create=True):
|
||||||
@ -646,28 +638,11 @@ def test_functions(
|
|||||||
except spack.repo.UnknownPackageError:
|
except spack.repo.UnknownPackageError:
|
||||||
tty.debug(f"{vname}: virtual does not appear to have a package file")
|
tty.debug(f"{vname}: virtual does not appear to have a package file")
|
||||||
|
|
||||||
# TODO (post-34236): Remove if removing empty test method check
|
|
||||||
def skip(line):
|
|
||||||
# This should match the lines in the deprecated test() method
|
|
||||||
ln = line.strip()
|
|
||||||
return ln.startswith("#") or ("warn" in ln and "deprecated" in ln)
|
|
||||||
|
|
||||||
doc_regex = r'\s+("""[\w\s\(\)\-\,\;\:]+""")'
|
|
||||||
tests = []
|
tests = []
|
||||||
for clss in classes:
|
for clss in classes:
|
||||||
methods = inspect.getmembers(clss, predicate=lambda x: inspect.isfunction(x))
|
methods = inspect.getmembers(clss, predicate=lambda x: inspect.isfunction(x))
|
||||||
for name, test_fn in methods:
|
for name, test_fn in methods:
|
||||||
# TODO (post-34236): "test"->"test_" once remove deprecated methods
|
if not name.startswith("test_"):
|
||||||
if not name.startswith("test"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# TODO (post-34236): Could remove empty method check once remove
|
|
||||||
# TODO (post-34236): deprecated methods though some use cases,
|
|
||||||
# TODO (post-34236): such as checking packages have actual, non-
|
|
||||||
# TODO (post-34236): empty tests, may want this check to remain.
|
|
||||||
source = re.sub(doc_regex, r"", inspect.getsource(test_fn)).splitlines()[1:]
|
|
||||||
lines = [ln.strip() for ln in source if not skip(ln)]
|
|
||||||
if not lines:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tests.append((clss.__name__, test_fn)) # type: ignore[union-attr]
|
tests.append((clss.__name__, test_fn)) # type: ignore[union-attr]
|
||||||
|
@ -55,17 +55,9 @@
|
|||||||
import spack.util.web
|
import spack.util.web
|
||||||
from spack.error import InstallError, NoURLError, PackageError
|
from spack.error import InstallError, NoURLError, PackageError
|
||||||
from spack.filesystem_view import YamlFilesystemView
|
from spack.filesystem_view import YamlFilesystemView
|
||||||
from spack.install_test import (
|
from spack.install_test import PackageTest, TestSuite
|
||||||
PackageTest,
|
|
||||||
TestFailure,
|
|
||||||
TestStatus,
|
|
||||||
TestSuite,
|
|
||||||
cache_extra_test_sources,
|
|
||||||
install_test_root,
|
|
||||||
)
|
|
||||||
from spack.solver.version_order import concretization_version_order
|
from spack.solver.version_order import concretization_version_order
|
||||||
from spack.stage import DevelopStage, ResourceStage, Stage, StageComposite, compute_stage_name
|
from spack.stage import DevelopStage, ResourceStage, Stage, StageComposite, compute_stage_name
|
||||||
from spack.util.executable import ProcessError, which
|
|
||||||
from spack.util.package_hash import package_hash
|
from spack.util.package_hash import package_hash
|
||||||
from spack.version import GitVersion, StandardVersion
|
from spack.version import GitVersion, StandardVersion
|
||||||
|
|
||||||
@ -1355,18 +1347,6 @@ def install_configure_args_path(self):
|
|||||||
"""Return the configure args file path on successful installation."""
|
"""Return the configure args file path on successful installation."""
|
||||||
return os.path.join(self.metadata_dir, _spack_configure_argsfile)
|
return os.path.join(self.metadata_dir, _spack_configure_argsfile)
|
||||||
|
|
||||||
# TODO (post-34236): Update tests and all packages that use this as a
|
|
||||||
# TODO (post-34236): package method to the function already available
|
|
||||||
# TODO (post-34236): to packages. Once done, remove this property.
|
|
||||||
@property
|
|
||||||
def install_test_root(self):
|
|
||||||
"""Return the install test root directory."""
|
|
||||||
tty.warn(
|
|
||||||
"The 'pkg.install_test_root' property is deprecated with removal "
|
|
||||||
"expected v0.23. Use 'install_test_root(pkg)' instead."
|
|
||||||
)
|
|
||||||
return install_test_root(self)
|
|
||||||
|
|
||||||
def archive_install_test_log(self):
|
def archive_install_test_log(self):
|
||||||
"""Archive the install-phase test log, if present."""
|
"""Archive the install-phase test log, if present."""
|
||||||
if getattr(self, "tester", None):
|
if getattr(self, "tester", None):
|
||||||
@ -1959,31 +1939,6 @@ def _resource_stage(self, resource):
|
|||||||
resource_stage_folder = "-".join(pieces)
|
resource_stage_folder = "-".join(pieces)
|
||||||
return resource_stage_folder
|
return resource_stage_folder
|
||||||
|
|
||||||
# TODO (post-34236): Update tests and all packages that use this as a
|
|
||||||
# TODO (post-34236): package method to the routine made available to
|
|
||||||
# TODO (post-34236): packages. Once done, remove this method.
|
|
||||||
def cache_extra_test_sources(self, srcs):
|
|
||||||
"""Copy relative source paths to the corresponding install test subdir
|
|
||||||
|
|
||||||
This method is intended as an optional install test setup helper for
|
|
||||||
grabbing source files/directories during the installation process and
|
|
||||||
copying them to the installation test subdirectory for subsequent use
|
|
||||||
during install testing.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
srcs (str or list): relative path for files and or
|
|
||||||
subdirectories located in the staged source path that are to
|
|
||||||
be copied to the corresponding location(s) under the install
|
|
||||||
testing directory.
|
|
||||||
"""
|
|
||||||
msg = (
|
|
||||||
"'pkg.cache_extra_test_sources(srcs) is deprecated with removal "
|
|
||||||
"expected in v0.23. Use 'cache_extra_test_sources(pkg, srcs)' "
|
|
||||||
"instead."
|
|
||||||
)
|
|
||||||
warnings.warn(msg)
|
|
||||||
cache_extra_test_sources(self, srcs)
|
|
||||||
|
|
||||||
def do_test(self, dirty=False, externals=False):
|
def do_test(self, dirty=False, externals=False):
|
||||||
if self.test_requires_compiler:
|
if self.test_requires_compiler:
|
||||||
compilers = spack.compilers.compilers_for_spec(
|
compilers = spack.compilers.compilers_for_spec(
|
||||||
@ -2007,178 +1962,6 @@ def do_test(self, dirty=False, externals=False):
|
|||||||
|
|
||||||
self.tester.stand_alone_tests(kwargs)
|
self.tester.stand_alone_tests(kwargs)
|
||||||
|
|
||||||
# TODO (post-34236): Remove this deprecated method when eliminate test,
|
|
||||||
# TODO (post-34236): run_test, etc.
|
|
||||||
@property
|
|
||||||
def _test_deprecated_warning(self):
|
|
||||||
alt = f"Use any name starting with 'test_' instead in {self.spec.name}."
|
|
||||||
return f"The 'test' method is deprecated. {alt}"
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove this deprecated method when eliminate test,
|
|
||||||
# TODO (post-34236): run_test, etc.
|
|
||||||
def test(self):
|
|
||||||
# Defer tests to virtual and concrete packages
|
|
||||||
warnings.warn(self._test_deprecated_warning)
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove this deprecated method when eliminate test,
|
|
||||||
# TODO (post-34236): run_test, etc.
|
|
||||||
def run_test(
|
|
||||||
self,
|
|
||||||
exe,
|
|
||||||
options=[],
|
|
||||||
expected=[],
|
|
||||||
status=0,
|
|
||||||
installed=False,
|
|
||||||
purpose=None,
|
|
||||||
skip_missing=False,
|
|
||||||
work_dir=None,
|
|
||||||
):
|
|
||||||
"""Run the test and confirm the expected results are obtained
|
|
||||||
|
|
||||||
Log any failures and continue, they will be re-raised later
|
|
||||||
|
|
||||||
Args:
|
|
||||||
exe (str): the name of the executable
|
|
||||||
options (str or list): list of options to pass to the runner
|
|
||||||
expected (str or list): list of expected output strings.
|
|
||||||
Each string is a regex expected to match part of the output.
|
|
||||||
status (int or list): possible passing status values
|
|
||||||
with 0 meaning the test is expected to succeed
|
|
||||||
installed (bool): if ``True``, the executable must be in the
|
|
||||||
install prefix
|
|
||||||
purpose (str): message to display before running test
|
|
||||||
skip_missing (bool): skip the test if the executable is not
|
|
||||||
in the install prefix bin directory or the provided work_dir
|
|
||||||
work_dir (str or None): path to the smoke test directory
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_title(purpose, test_name):
|
|
||||||
if not purpose:
|
|
||||||
return f"test: {test_name}: execute {test_name}"
|
|
||||||
|
|
||||||
match = re.search(r"test: ([^:]*): (.*)", purpose)
|
|
||||||
if match:
|
|
||||||
# The test title has all the expected parts
|
|
||||||
return purpose
|
|
||||||
|
|
||||||
match = re.search(r"test: (.*)", purpose)
|
|
||||||
if match:
|
|
||||||
reason = match.group(1)
|
|
||||||
return f"test: {test_name}: {reason}"
|
|
||||||
|
|
||||||
return f"test: {test_name}: {purpose}"
|
|
||||||
|
|
||||||
base_exe = os.path.basename(exe)
|
|
||||||
alternate = f"Use 'test_part' instead for {self.spec.name} to process {base_exe}."
|
|
||||||
warnings.warn(f"The 'run_test' method is deprecated. {alternate}")
|
|
||||||
|
|
||||||
extra = re.compile(r"[\s,\- ]")
|
|
||||||
details = (
|
|
||||||
[extra.sub("", options)]
|
|
||||||
if isinstance(options, str)
|
|
||||||
else [extra.sub("", os.path.basename(opt)) for opt in options]
|
|
||||||
)
|
|
||||||
details = "_".join([""] + details) if details else ""
|
|
||||||
test_name = f"test_{base_exe}{details}"
|
|
||||||
tty.info(test_title(purpose, test_name), format="g")
|
|
||||||
|
|
||||||
wdir = "." if work_dir is None else work_dir
|
|
||||||
with fsys.working_dir(wdir, create=True):
|
|
||||||
try:
|
|
||||||
runner = which(exe)
|
|
||||||
if runner is None and skip_missing:
|
|
||||||
self.tester.status(test_name, TestStatus.SKIPPED, f"{exe} is missing")
|
|
||||||
return
|
|
||||||
assert runner is not None, f"Failed to find executable '{exe}'"
|
|
||||||
|
|
||||||
self._run_test_helper(runner, options, expected, status, installed, purpose)
|
|
||||||
self.tester.status(test_name, TestStatus.PASSED, None)
|
|
||||||
return True
|
|
||||||
except (AssertionError, BaseException) as e:
|
|
||||||
# print a summary of the error to the log file
|
|
||||||
# so that cdash and junit reporters know about it
|
|
||||||
exc_type, _, tb = sys.exc_info()
|
|
||||||
|
|
||||||
self.tester.status(test_name, TestStatus.FAILED, str(e))
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
# remove the current call frame to exclude the extract_stack
|
|
||||||
# call from the error
|
|
||||||
stack = traceback.extract_stack()[:-1]
|
|
||||||
|
|
||||||
# Package files have a line added at import time, so we re-read
|
|
||||||
# the file to make line numbers match. We have to subtract two
|
|
||||||
# from the line number because the original line number is
|
|
||||||
# inflated once by the import statement and the lines are
|
|
||||||
# displaced one by the import statement.
|
|
||||||
for i, entry in enumerate(stack):
|
|
||||||
filename, lineno, function, text = entry
|
|
||||||
if spack.repo.is_package_file(filename):
|
|
||||||
with open(filename, "r") as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
new_lineno = lineno - 2
|
|
||||||
text = lines[new_lineno]
|
|
||||||
stack[i] = (filename, new_lineno, function, text)
|
|
||||||
|
|
||||||
# Format the stack to print and print it
|
|
||||||
out = traceback.format_list(stack)
|
|
||||||
for line in out:
|
|
||||||
print(line.rstrip("\n"))
|
|
||||||
|
|
||||||
if exc_type is spack.util.executable.ProcessError:
|
|
||||||
out = io.StringIO()
|
|
||||||
spack.build_environment.write_log_summary(
|
|
||||||
out, "test", self.tester.test_log_file, last=1
|
|
||||||
)
|
|
||||||
m = out.getvalue()
|
|
||||||
else:
|
|
||||||
# We're below the package context, so get context from
|
|
||||||
# stack instead of from traceback.
|
|
||||||
# The traceback is truncated here, so we can't use it to
|
|
||||||
# traverse the stack.
|
|
||||||
context = spack.build_environment.get_package_context(tb)
|
|
||||||
m = "\n".join(context) if context else ""
|
|
||||||
|
|
||||||
exc = e # e is deleted after this block
|
|
||||||
|
|
||||||
# If we fail fast, raise another error
|
|
||||||
if spack.config.get("config:fail_fast", False):
|
|
||||||
raise TestFailure([(exc, m)])
|
|
||||||
else:
|
|
||||||
self.tester.add_failure(exc, m)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove this deprecated method when eliminate test,
|
|
||||||
# TODO (post-34236): run_test, etc.
|
|
||||||
def _run_test_helper(self, runner, options, expected, status, installed, purpose):
|
|
||||||
status = [status] if isinstance(status, int) else status
|
|
||||||
expected = [expected] if isinstance(expected, str) else expected
|
|
||||||
options = [options] if isinstance(options, str) else options
|
|
||||||
|
|
||||||
if installed:
|
|
||||||
msg = f"Executable '{runner.name}' expected in prefix, "
|
|
||||||
msg += f"found in {runner.path} instead"
|
|
||||||
assert runner.path.startswith(self.spec.prefix), msg
|
|
||||||
|
|
||||||
tty.msg(f"Expecting return code in {status}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
output = runner(*options, output=str.split, error=str.split)
|
|
||||||
|
|
||||||
assert 0 in status, f"Expected {runner.name} execution to fail"
|
|
||||||
except ProcessError as err:
|
|
||||||
output = str(err)
|
|
||||||
match = re.search(r"exited with status ([0-9]+)", output)
|
|
||||||
if not (match and int(match.group(1)) in status):
|
|
||||||
raise
|
|
||||||
|
|
||||||
for check in expected:
|
|
||||||
cmd = " ".join([runner.name] + options)
|
|
||||||
msg = f"Expected '{check}' to match output of `{cmd}`"
|
|
||||||
msg += f"\n\nOutput: {output}"
|
|
||||||
assert re.search(check, output), msg
|
|
||||||
|
|
||||||
def unit_test_check(self):
|
def unit_test_check(self):
|
||||||
"""Hook for unit tests to assert things about package internals.
|
"""Hook for unit tests to assert things about package internals.
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import xml.sax.saxutils
|
import xml.sax.saxutils
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@ -42,17 +41,6 @@ def elapsed(current, previous):
|
|||||||
return diff.total_seconds()
|
return diff.total_seconds()
|
||||||
|
|
||||||
|
|
||||||
# TODO (post-34236): Should remove with deprecated test methods since don't
|
|
||||||
# TODO (post-34236): have an XFAIL mechanism with the new test_part() approach.
|
|
||||||
def expected_failure(line):
|
|
||||||
if not line:
|
|
||||||
return False
|
|
||||||
|
|
||||||
match = returns_regexp.search(line)
|
|
||||||
xfail = "0" not in match.group(1) if match else False
|
|
||||||
return xfail
|
|
||||||
|
|
||||||
|
|
||||||
def new_part():
|
def new_part():
|
||||||
return {
|
return {
|
||||||
"command": None,
|
"command": None,
|
||||||
@ -66,14 +54,6 @@ def new_part():
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove this when remove deprecated methods
|
|
||||||
def part_name(source):
|
|
||||||
elements = []
|
|
||||||
for e in source.replace("'", "").split(" "):
|
|
||||||
elements.append(os.path.basename(e) if os.sep in e else e)
|
|
||||||
return "_".join(elements)
|
|
||||||
|
|
||||||
|
|
||||||
def process_part_end(part, curr_time, last_time):
|
def process_part_end(part, curr_time, last_time):
|
||||||
if part:
|
if part:
|
||||||
if not part["elapsed"]:
|
if not part["elapsed"]:
|
||||||
@ -81,11 +61,7 @@ def process_part_end(part, curr_time, last_time):
|
|||||||
|
|
||||||
stat = part["status"]
|
stat = part["status"]
|
||||||
if stat in completed:
|
if stat in completed:
|
||||||
# TODO (post-34236): remove the expected failure mapping when
|
if part["completed"] == "Unknown":
|
||||||
# TODO (post-34236): remove deprecated test methods.
|
|
||||||
if stat == "passed" and expected_failure(part["desc"]):
|
|
||||||
part["completed"] = "Expected to fail"
|
|
||||||
elif part["completed"] == "Unknown":
|
|
||||||
part["completed"] = completed[stat]
|
part["completed"] = completed[stat]
|
||||||
elif stat is None or stat == "unknown":
|
elif stat is None or stat == "unknown":
|
||||||
part["status"] = "passed"
|
part["status"] = "passed"
|
||||||
@ -153,14 +129,6 @@ def extract_test_parts(default_name, outputs):
|
|||||||
if msg.startswith("Installing"):
|
if msg.startswith("Installing"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# TODO (post-34236): Remove this check when remove run_test(),
|
|
||||||
# TODO (post-34236): etc. since no longer supporting expected
|
|
||||||
# TODO (post-34236): failures.
|
|
||||||
if msg.startswith("Expecting return code"):
|
|
||||||
if part:
|
|
||||||
part["desc"] += f"; {msg}"
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Terminate without further parsing if no more test messages
|
# Terminate without further parsing if no more test messages
|
||||||
if "Completed testing" in msg:
|
if "Completed testing" in msg:
|
||||||
# Process last lingering part IF it didn't generate status
|
# Process last lingering part IF it didn't generate status
|
||||||
|
@ -27,8 +27,15 @@
|
|||||||
(["invalid-gitlab-patch-url"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
(["invalid-gitlab-patch-url"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
||||||
# This package has invalid GitLab patch URLs
|
# This package has invalid GitLab patch URLs
|
||||||
(["invalid-selfhosted-gitlab-patch-url"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
(["invalid-selfhosted-gitlab-patch-url"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
||||||
# This package has a stand-alone 'test*' method in build-time callbacks
|
# This package has a stand-alone test method in build-time callbacks
|
||||||
(["fail-test-audit"], ["PKG-DIRECTIVES", "PKG-PROPERTIES"]),
|
(["fail-test-audit"], ["PKG-PROPERTIES"]),
|
||||||
|
# This package implements and uses several deprecated stand-alone
|
||||||
|
# test methods
|
||||||
|
(["fail-test-audit-deprecated"], ["PKG-DEPRECATED-ATTRIBUTES"]),
|
||||||
|
# This package has stand-alone test methods without non-trivial docstrings
|
||||||
|
(["fail-test-audit-docstring"], ["PKG-PROPERTIES"]),
|
||||||
|
# This package has a stand-alone test method without an implementation
|
||||||
|
(["fail-test-audit-impl"], ["PKG-PROPERTIES"]),
|
||||||
# This package has no issues
|
# This package has no issues
|
||||||
(["mpileaks"], None),
|
(["mpileaks"], None),
|
||||||
# This package has a conflict with a trigger which cannot constrain the constraint
|
# This package has a conflict with a trigger which cannot constrain the constraint
|
||||||
@ -41,7 +48,7 @@ def test_package_audits(packages, expected_errors, mock_packages):
|
|||||||
|
|
||||||
# Check that errors were reported only for the expected failure
|
# Check that errors were reported only for the expected failure
|
||||||
actual_errors = [check for check, errors in reports if errors]
|
actual_errors = [check for check, errors in reports if errors]
|
||||||
msg = [str(e) for _, errors in reports for e in errors]
|
msg = "\n".join([str(e) for _, errors in reports for e in errors])
|
||||||
if expected_errors:
|
if expected_errors:
|
||||||
assert expected_errors == actual_errors, msg
|
assert expected_errors == actual_errors, msg
|
||||||
else:
|
else:
|
||||||
|
@ -194,6 +194,9 @@ def test_test_list_all(mock_packages):
|
|||||||
assert set(pkgs) == set(
|
assert set(pkgs) == set(
|
||||||
[
|
[
|
||||||
"fail-test-audit",
|
"fail-test-audit",
|
||||||
|
"fail-test-audit-deprecated",
|
||||||
|
"fail-test-audit-docstring",
|
||||||
|
"fail-test-audit-impl",
|
||||||
"mpich",
|
"mpich",
|
||||||
"perl-extension",
|
"perl-extension",
|
||||||
"printing-package",
|
"printing-package",
|
||||||
|
@ -357,37 +357,28 @@ class Legion(CMakePackage):
|
|||||||
install test subdirectory for use during `spack test run`."""
|
install test subdirectory for use during `spack test run`."""
|
||||||
cache_extra_test_sources(self, [join_path('examples', 'local_function_tasks')])
|
cache_extra_test_sources(self, [join_path('examples', 'local_function_tasks')])
|
||||||
|
|
||||||
def run_local_function_tasks_test(self):
|
def test_run_local_function_tasks(self):
|
||||||
"""Run stand alone test: local_function_tasks"""
|
"""Build and run external application example"""
|
||||||
|
|
||||||
test_dir = join_path(self.test_suite.current_test_cache_dir,
|
test_dir = join_path(
|
||||||
'examples', 'local_function_tasks')
|
self.test_suite.current_test_cache_dir, "examples", "local_function_tasks"
|
||||||
|
)
|
||||||
|
|
||||||
if not os.path.exists(test_dir):
|
if not os.path.exists(test_dir):
|
||||||
print('Skipping local_function_tasks test')
|
raise SkipTest(f"{test_dir} must exist")
|
||||||
return
|
|
||||||
|
|
||||||
exe = 'local_function_tasks'
|
cmake_args = [
|
||||||
|
f"-DCMAKE_C_COMPILER={self.compiler.cc}",
|
||||||
|
f"-DCMAKE_CXX_COMPILER={self.compiler.cxx}",
|
||||||
|
f"-DLegion_DIR={join_path(self.prefix, 'share', 'Legion', 'cmake')}",
|
||||||
|
]
|
||||||
|
|
||||||
cmake_args = ['-DCMAKE_C_COMPILER={0}'.format(self.compiler.cc),
|
with working_dir(test_dir):
|
||||||
'-DCMAKE_CXX_COMPILER={0}'.format(self.compiler.cxx),
|
cmake = self.spec["cmake"].command
|
||||||
'-DLegion_DIR={0}'.format(join_path(self.prefix,
|
cmake(*cmake_args)
|
||||||
'share',
|
|
||||||
'Legion',
|
|
||||||
'cmake'))]
|
|
||||||
|
|
||||||
self.run_test('cmake',
|
make = which("make")
|
||||||
options=cmake_args,
|
make()
|
||||||
purpose='test: generate makefile for {0} example'.format(exe),
|
|
||||||
work_dir=test_dir)
|
|
||||||
|
|
||||||
self.run_test('make',
|
exe = which("local_function_tasks")
|
||||||
purpose='test: build {0} example'.format(exe),
|
exe()
|
||||||
work_dir=test_dir)
|
|
||||||
|
|
||||||
self.run_test(exe,
|
|
||||||
purpose='test: run {0} example'.format(exe),
|
|
||||||
work_dir=test_dir)
|
|
||||||
|
|
||||||
def test(self):
|
|
||||||
self.run_local_function_tasks_test()
|
|
||||||
|
@ -816,40 +816,22 @@ class Mfem(Package, CudaPackage, ROCmPackage):
|
|||||||
install test subdirectory for use during `spack test run`."""
|
install test subdirectory for use during `spack test run`."""
|
||||||
cache_extra_test_sources(self, [self.examples_src_dir, self.examples_data_dir])
|
cache_extra_test_sources(self, [self.examples_src_dir, self.examples_data_dir])
|
||||||
|
|
||||||
def test(self):
|
def test_ex10(self):
|
||||||
test_dir = join_path(
|
"""build and run ex10(p)"""
|
||||||
self.test_suite.current_test_cache_dir,
|
|
||||||
self.examples_src_dir
|
|
||||||
)
|
|
||||||
|
|
||||||
# MFEM has many examples to serve as a suitable smoke check. ex10
|
# MFEM has many examples to serve as a suitable smoke check. ex10
|
||||||
# was chosen arbitrarily among the examples that work both with
|
# was chosen arbitrarily among the examples that work both with
|
||||||
# MPI and without it
|
# MPI and without it
|
||||||
test_exe = 'ex10p' if ('+mpi' in self.spec) else 'ex10'
|
test_dir = join_path(self.test_suite.current_test_cache_dir, self.examples_src_dir)
|
||||||
self.run_test(
|
|
||||||
'make',
|
|
||||||
[
|
|
||||||
'CONFIG_MK={0}/share/mfem/config.mk'.format(self.prefix),
|
|
||||||
test_exe,
|
|
||||||
'parallel=False'
|
|
||||||
],
|
|
||||||
purpose='test: building {0}'.format(test_exe),
|
|
||||||
skip_missing=False,
|
|
||||||
work_dir=test_dir,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.run_test(
|
mesh = join_path("..", self.examples_data_dir, "beam-quad.mesh")
|
||||||
'./{0}'.format(test_exe),
|
test_exe = "ex10p" if ("+mpi" in self.spec) else "ex10"
|
||||||
[
|
|
||||||
'--mesh',
|
with working_dir(test_dir):
|
||||||
'../{0}/beam-quad.mesh'.format(self.examples_data_dir)
|
make = which("make")
|
||||||
],
|
make(f"CONFIG_MK={self.config_mk}", test_exe, "parallel=False")
|
||||||
[],
|
|
||||||
installed=False,
|
ex10 = which(test_exe)
|
||||||
purpose='test: running {0}'.format(test_exe),
|
ex10("--mesh", mesh)
|
||||||
skip_missing=False,
|
|
||||||
work_dir=test_dir,
|
|
||||||
)
|
|
||||||
|
|
||||||
# this patch is only needed for mfem 4.1, where a few
|
# this patch is only needed for mfem 4.1, where a few
|
||||||
# released files include byte order marks
|
# released files include byte order marks
|
||||||
|
@ -286,56 +286,3 @@ def compilers(compiler, arch_spec):
|
|||||||
error = capfd.readouterr()[1]
|
error = capfd.readouterr()[1]
|
||||||
assert "Skipping tests for package" in error
|
assert "Skipping tests for package" in error
|
||||||
assert "test requires missing compiler" in error
|
assert "test requires missing compiler" in error
|
||||||
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove when remove deprecated run_test(), etc.
|
|
||||||
@pytest.mark.not_on_windows("echo not available on Windows")
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"msg,installed,purpose,expected",
|
|
||||||
[
|
|
||||||
("do-nothing", False, "test: echo", "do-nothing"),
|
|
||||||
("not installed", True, "test: echo not installed", "expected in prefix"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_package_run_test_install(
|
|
||||||
install_mockery, mock_fetch, capfd, msg, installed, purpose, expected
|
|
||||||
):
|
|
||||||
"""Confirm expected outputs from run_test for installed/not installed exe."""
|
|
||||||
s = spack.spec.Spec("trivial-smoke-test").concretized()
|
|
||||||
pkg = s.package
|
|
||||||
|
|
||||||
pkg.run_test(
|
|
||||||
"echo", msg, expected=[expected], installed=installed, purpose=purpose, work_dir="."
|
|
||||||
)
|
|
||||||
output = capfd.readouterr()[0]
|
|
||||||
assert expected in output
|
|
||||||
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove when remove deprecated run_test(), etc.
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"skip,failures,status",
|
|
||||||
[
|
|
||||||
(True, 0, str(spack.install_test.TestStatus.SKIPPED)),
|
|
||||||
(False, 1, str(spack.install_test.TestStatus.FAILED)),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_package_run_test_missing(install_mockery, mock_fetch, capfd, skip, failures, status):
|
|
||||||
"""Confirm expected results from run_test for missing exe when skip or not."""
|
|
||||||
s = spack.spec.Spec("trivial-smoke-test").concretized()
|
|
||||||
pkg = s.package
|
|
||||||
|
|
||||||
pkg.run_test("no-possible-program", skip_missing=skip)
|
|
||||||
output = capfd.readouterr()[0]
|
|
||||||
assert len(pkg.tester.test_failures) == failures
|
|
||||||
assert status in output
|
|
||||||
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove when remove deprecated run_test(), etc.
|
|
||||||
def test_package_run_test_fail_fast(install_mockery, mock_fetch):
|
|
||||||
"""Confirm expected exception when run_test with fail_fast enabled."""
|
|
||||||
s = spack.spec.Spec("trivial-smoke-test").concretized()
|
|
||||||
pkg = s.package
|
|
||||||
|
|
||||||
with spack.config.override("config:fail_fast", True):
|
|
||||||
with pytest.raises(spack.install_test.TestFailure, match="Failed to find executable"):
|
|
||||||
pkg.run_test("no-possible-program")
|
|
||||||
|
@ -120,26 +120,6 @@ def test_reporters_extract_missing_desc():
|
|||||||
assert parts[2]["command"] == "exe1 1; exe2 2"
|
assert parts[2]["command"] == "exe1 1; exe2 2"
|
||||||
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove this test when removing deprecated run_test(), etc.
|
|
||||||
def test_reporters_extract_xfail():
|
|
||||||
fake_bin = fs.join_path(fake_install_prefix, "bin", "fake-app")
|
|
||||||
outputs = """
|
|
||||||
==> Testing package fake-1.0-abcdefg
|
|
||||||
==> [2022-02-15-18:44:21.250165] test: test_fake: Checking fake imports
|
|
||||||
==> [2022-02-15-18:44:21.250175] Expecting return code in [3]
|
|
||||||
==> [2022-02-15-18:44:21.250200] '{0}'
|
|
||||||
{1}
|
|
||||||
""".format(
|
|
||||||
fake_bin, str(TestStatus.PASSED)
|
|
||||||
).splitlines()
|
|
||||||
|
|
||||||
parts = spack.reporters.extract.extract_test_parts("fake", outputs)
|
|
||||||
|
|
||||||
assert len(parts) == 1
|
|
||||||
parts[0]["command"] == fake_bin
|
|
||||||
parts[0]["completed"] == "Expected to fail"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("state", [("not installed"), ("external")])
|
@pytest.mark.parametrize("state", [("not installed"), ("external")])
|
||||||
def test_reporters_extract_skipped(state):
|
def test_reporters_extract_skipped(state):
|
||||||
expected = "Skipped {0} package".format(state)
|
expected = "Skipped {0} package".format(state)
|
||||||
@ -156,34 +136,6 @@ def test_reporters_extract_skipped(state):
|
|||||||
parts[0]["completed"] == expected
|
parts[0]["completed"] == expected
|
||||||
|
|
||||||
|
|
||||||
# TODO (post-34236): Remove this test when removing deprecated run_test(), etc.
|
|
||||||
def test_reporters_skip():
|
|
||||||
# This test ticks 3 boxes:
|
|
||||||
# 1) covers an as yet uncovered skip messages
|
|
||||||
# 2) covers debug timestamps
|
|
||||||
# 3) unrecognized output
|
|
||||||
fake_bin = fs.join_path(fake_install_prefix, "bin", "fake")
|
|
||||||
unknown_message = "missing timestamp"
|
|
||||||
outputs = """
|
|
||||||
==> Testing package fake-1.0-abcdefg
|
|
||||||
==> [2022-02-15-18:44:21.250165, 123456] Detected the following modules: fake1
|
|
||||||
==> {0}
|
|
||||||
==> [2022-02-15-18:44:21.250175, 123456] test: test_fake: running fake program
|
|
||||||
==> [2022-02-15-18:44:21.250200, 123456] '{1}'
|
|
||||||
INVALID
|
|
||||||
Results for test suite abcdefghijklmn
|
|
||||||
""".format(
|
|
||||||
unknown_message, fake_bin
|
|
||||||
).splitlines()
|
|
||||||
|
|
||||||
parts = spack.reporters.extract.extract_test_parts("fake", outputs)
|
|
||||||
|
|
||||||
assert len(parts) == 1
|
|
||||||
assert fake_bin in parts[0]["command"]
|
|
||||||
assert parts[0]["loglines"] == ["INVALID"]
|
|
||||||
assert parts[0]["elapsed"] == 0.0
|
|
||||||
|
|
||||||
|
|
||||||
def test_reporters_skip_new():
|
def test_reporters_skip_new():
|
||||||
outputs = """
|
outputs = """
|
||||||
==> [2023-04-06-15:55:13.094025] test: test_skip:
|
==> [2023-04-06-15:55:13.094025] test: test_skip:
|
||||||
|
@ -338,15 +338,15 @@ def test_remove_complex_package_logic_filtered():
|
|||||||
("grads", "rrlmwml3f2frdnqavmro3ias66h5b2ce"),
|
("grads", "rrlmwml3f2frdnqavmro3ias66h5b2ce"),
|
||||||
("llvm", "nufffum5dabmaf4l5tpfcblnbfjknvd3"),
|
("llvm", "nufffum5dabmaf4l5tpfcblnbfjknvd3"),
|
||||||
# has @when("@4.1.0") and raw unicode literals
|
# has @when("@4.1.0") and raw unicode literals
|
||||||
("mfem", "lbhr43gm5zdye2yhqznucxb4sg6vhryl"),
|
("mfem", "whwftpqbjvzncmb52oz6izkanbha2uji"),
|
||||||
("mfem@4.0.0", "lbhr43gm5zdye2yhqznucxb4sg6vhryl"),
|
("mfem@4.0.0", "whwftpqbjvzncmb52oz6izkanbha2uji"),
|
||||||
("mfem@4.1.0", "vjdjdgjt6nyo7ited2seki5epggw5gza"),
|
("mfem@4.1.0", "bpi7of3xelo7fr3ta2lm6bmiruijnxcg"),
|
||||||
# has @when("@1.5.0:")
|
# has @when("@1.5.0:")
|
||||||
("py-torch", "qs7djgqn7dy7r3ps4g7hv2pjvjk4qkhd"),
|
("py-torch", "qs7djgqn7dy7r3ps4g7hv2pjvjk4qkhd"),
|
||||||
("py-torch@1.0", "qs7djgqn7dy7r3ps4g7hv2pjvjk4qkhd"),
|
("py-torch@1.0", "qs7djgqn7dy7r3ps4g7hv2pjvjk4qkhd"),
|
||||||
("py-torch@1.6", "p4ine4hc6f2ik2f2wyuwieslqbozll5w"),
|
("py-torch@1.6", "p4ine4hc6f2ik2f2wyuwieslqbozll5w"),
|
||||||
# has a print with multiple arguments
|
# has a print with multiple arguments
|
||||||
("legion", "efpfd2c4pzhsbyc3o7plqcmtwm6b57yh"),
|
("legion", "bq2etsik5l6pbryxmbhfhzynci56ruy4"),
|
||||||
# has nested `with when()` blocks and loops
|
# has nested `with when()` blocks and loops
|
||||||
("trilinos", "vqrgscjrla4hi7bllink7v6v6dwxgc2p"),
|
("trilinos", "vqrgscjrla4hi7bllink7v6v6dwxgc2p"),
|
||||||
],
|
],
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
from spack.package import *
|
||||||
|
|
||||||
|
|
||||||
|
class FailTestAuditDeprecated(MakefilePackage):
|
||||||
|
"""Simple package attempting to implement and use deprecated stand-alone test methods."""
|
||||||
|
|
||||||
|
homepage = "http://github.com/dummy/fail-test-audit-deprecated"
|
||||||
|
url = "https://github.com/dummy/fail-test-audit-deprecated/archive/v1.0.tar.gz"
|
||||||
|
|
||||||
|
version("2.0", sha256="c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1")
|
||||||
|
version("1.0", sha256="abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234")
|
||||||
|
|
||||||
|
@run_after("install")
|
||||||
|
def copy_test_files(self):
|
||||||
|
"""test that uses the deprecated install_test_root method"""
|
||||||
|
self.cache_extra_test_sources(".")
|
||||||
|
|
||||||
|
def test(self):
|
||||||
|
"""this is a deprecated reserved method for stand-alone testing"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_use_install_test_root(self):
|
||||||
|
"""use the deprecated install_test_root method"""
|
||||||
|
print(f"install test root = {self.install_test_root()}")
|
||||||
|
|
||||||
|
def test_run_test(self):
|
||||||
|
"""use the deprecated run_test method"""
|
||||||
|
self.run_test("which", ["make"])
|
@ -0,0 +1,24 @@
|
|||||||
|
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
from spack.package import *
|
||||||
|
|
||||||
|
|
||||||
|
class FailTestAuditDocstring(MakefilePackage):
|
||||||
|
"""Simple package with a stand-alone test that is missing its docstring."""
|
||||||
|
|
||||||
|
homepage = "http://github.com/dummy/fail-test-audit-docstring"
|
||||||
|
url = "https://github.com/dummy/fail-test-audit-docstring/archive/v1.0.tar.gz"
|
||||||
|
|
||||||
|
version("2.0", sha256="c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1")
|
||||||
|
version("1.0", sha256="abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234")
|
||||||
|
|
||||||
|
# The required docstring is missing.
|
||||||
|
def test_missing_docstring(self):
|
||||||
|
print("Ran test_missing_docstring")
|
||||||
|
|
||||||
|
# The required docstring is effectively empty.
|
||||||
|
def test_empty_docstring(self):
|
||||||
|
""" """
|
||||||
|
print("Ran test_empty_docstring")
|
@ -0,0 +1,21 @@
|
|||||||
|
# Copyright 2013-2024 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
from spack.package import *
|
||||||
|
|
||||||
|
|
||||||
|
class FailTestAuditImpl(MakefilePackage):
|
||||||
|
"""Simple package that is missing the stand-alone test implementation."""
|
||||||
|
|
||||||
|
homepage = "http://github.com/dummy/fail-test-audit-impl"
|
||||||
|
url = "https://github.com/dummy/fail-test-audit-impl/archive/v1.0.tar.gz"
|
||||||
|
|
||||||
|
version("2.0", sha256="c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1")
|
||||||
|
version("1.0", sha256="abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234")
|
||||||
|
|
||||||
|
# The test method has not been implemented.
|
||||||
|
def test_no_impl(self):
|
||||||
|
"""test sans implementation"""
|
||||||
|
# this comment should not matter
|
||||||
|
pass
|
@ -6,16 +6,17 @@
|
|||||||
|
|
||||||
|
|
||||||
class FailTestAudit(MakefilePackage):
|
class FailTestAudit(MakefilePackage):
|
||||||
"""Simple package with one optional dependency"""
|
"""Simple package attempting to re-use stand-alone test method as a build check."""
|
||||||
|
|
||||||
homepage = "http://www.example.com"
|
homepage = "http://github.com/dummy/fail-test-audit"
|
||||||
url = "http://www.example.com/a-1.0.tar.gz"
|
url = "https://github.com/dummy/fail-test-audit/archive/v1.0.tar.gz"
|
||||||
|
|
||||||
version("1.0", md5="0123456789abcdef0123456789abcdef")
|
version("2.0", sha256="c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1")
|
||||||
version("2.0", md5="abcdef0123456789abcdef0123456789")
|
version("1.0", sha256="abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234abcd1234")
|
||||||
|
|
||||||
|
# Stand-alone test methods cannot be included in build_time_test_callbacks
|
||||||
build_time_test_callbacks = ["test_build_callbacks"]
|
build_time_test_callbacks = ["test_build_callbacks"]
|
||||||
|
|
||||||
def test_build_callbacks(self):
|
def test_build_callbacks(self):
|
||||||
"""test build time test callbacks"""
|
"""test build time test callbacks failure"""
|
||||||
print("test-build-callbacks")
|
print("test_build_callbacks")
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
import spack.pkg.builtin.mock.python as mp
|
import spack.pkg.builtin.mock.python as mp
|
||||||
|
from spack.build_systems._checks import BaseBuilder, execute_install_time_tests
|
||||||
from spack.package import *
|
from spack.package import *
|
||||||
|
|
||||||
|
|
||||||
@ -13,8 +14,11 @@ class PyTestCallback(mp.Python):
|
|||||||
homepage = "http://www.example.com"
|
homepage = "http://www.example.com"
|
||||||
url = "http://www.example.com/test-callback-1.0.tar.gz"
|
url = "http://www.example.com/test-callback-1.0.tar.gz"
|
||||||
|
|
||||||
# TODO (post-34236): "test" -> "test_callback" once remove "test" support
|
#: This attribute is used in UI queries that need to know the build
|
||||||
install_time_test_callbacks = ["test"]
|
#: system base class
|
||||||
|
build_system_class = "PyTestCallback"
|
||||||
|
|
||||||
|
build_system("testcallback")
|
||||||
|
|
||||||
version("1.0", "00000000000000000000000000000110")
|
version("1.0", "00000000000000000000000000000110")
|
||||||
version("2.0", "00000000000000000000000000000120")
|
version("2.0", "00000000000000000000000000000120")
|
||||||
@ -22,8 +26,21 @@ class PyTestCallback(mp.Python):
|
|||||||
def install(self, spec, prefix):
|
def install(self, spec, prefix):
|
||||||
mkdirp(prefix.bin)
|
mkdirp(prefix.bin)
|
||||||
|
|
||||||
# TODO (post-34236): "test" -> "test_callback" once remove "test" support
|
def test_callback(self):
|
||||||
def test(self):
|
|
||||||
super().test()
|
|
||||||
|
|
||||||
print("PyTestCallback test")
|
print("PyTestCallback test")
|
||||||
|
|
||||||
|
|
||||||
|
@spack.builder.builder("testcallback")
|
||||||
|
class MyBuilder(BaseBuilder):
|
||||||
|
phases = ("install",)
|
||||||
|
|
||||||
|
#: Callback names for install-time test
|
||||||
|
install_time_test_callbacks = ["test_callback"]
|
||||||
|
|
||||||
|
def install(self, pkg, spec, prefix):
|
||||||
|
pkg.install(spec, prefix)
|
||||||
|
|
||||||
|
spack.builder.run_after("install")(execute_install_time_tests)
|
||||||
|
|
||||||
|
def test_callback(self):
|
||||||
|
self.pkg.test_callback()
|
||||||
|
@ -21,9 +21,9 @@ def install(self, spec, prefix):
|
|||||||
|
|
||||||
@run_before("install")
|
@run_before("install")
|
||||||
def create_extra_test_source(self):
|
def create_extra_test_source(self):
|
||||||
mkdirp(self.install_test_root)
|
mkdirp(install_test_root(self))
|
||||||
touch(join_path(self.install_test_root, self.test_source_filename))
|
touch(join_path(install_test_root(self), self.test_source_filename))
|
||||||
|
|
||||||
@run_after("install")
|
@run_after("install")
|
||||||
def copy_test_sources(self):
|
def copy_test_sources(self):
|
||||||
self.cache_extra_test_sources([self.test_source_filename])
|
cache_extra_test_sources(self, [self.test_source_filename])
|
||||||
|
@ -224,6 +224,7 @@ def flag_handler(self, name, flags):
|
|||||||
return (iflags, None, flags)
|
return (iflags, None, flags)
|
||||||
|
|
||||||
def test_binaries(self):
|
def test_binaries(self):
|
||||||
|
"""check versions reported by binaries"""
|
||||||
binaries = [
|
binaries = [
|
||||||
"ar",
|
"ar",
|
||||||
"c++filt",
|
"c++filt",
|
||||||
|
@ -78,6 +78,7 @@ def install(self, spec, prefix):
|
|||||||
install_tree(".", prefix)
|
install_tree(".", prefix)
|
||||||
|
|
||||||
def test_cpmd(self):
|
def test_cpmd(self):
|
||||||
|
"""check cpmd.x outputs"""
|
||||||
test_dir = self.test_suite.current_test_data_dir
|
test_dir = self.test_suite.current_test_data_dir
|
||||||
test_file = join_path(test_dir, "1-h2o-pbc-geoopt.inp")
|
test_file = join_path(test_dir, "1-h2o-pbc-geoopt.inp")
|
||||||
opts = []
|
opts = []
|
||||||
|
@ -620,7 +620,7 @@ def cache_test_sources(self):
|
|||||||
cache_extra_test_sources(self, [self.test_src_dir])
|
cache_extra_test_sources(self, [self.test_src_dir])
|
||||||
|
|
||||||
def test_samples(self):
|
def test_samples(self):
|
||||||
# configure, build and run all hip samples
|
"""build and run all hip samples"""
|
||||||
if self.spec.satisfies("@5.1:5.5"):
|
if self.spec.satisfies("@5.1:5.5"):
|
||||||
test_dir = join_path(self.test_suite.current_test_cache_dir, self.test_src_dir_old)
|
test_dir = join_path(self.test_suite.current_test_cache_dir, self.test_src_dir_old)
|
||||||
elif self.spec.satisfies("@5.6:"):
|
elif self.spec.satisfies("@5.6:"):
|
||||||
|
@ -76,7 +76,7 @@ def setup_build_tests(self):
|
|||||||
print("Skipping copy of stand-alone test files: requires +mpi build")
|
print("Skipping copy of stand-alone test files: requires +mpi build")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.cache_extra_test_sources(".")
|
cache_extra_test_sources(self, ".")
|
||||||
|
|
||||||
# Clean cached makefiles now so only done once
|
# Clean cached makefiles now so only done once
|
||||||
print("Converting cached Makefile for stand-alone test use")
|
print("Converting cached Makefile for stand-alone test use")
|
||||||
|
@ -1315,7 +1315,7 @@ def test_mpirun(self):
|
|||||||
self.run_installed_binary("mpirun", options, [f"openmpi-{self.spec.version}"])
|
self.run_installed_binary("mpirun", options, [f"openmpi-{self.spec.version}"])
|
||||||
|
|
||||||
def test_opmpi_info(self):
|
def test_opmpi_info(self):
|
||||||
"""test installed mpirun"""
|
"""test installed ompi_info"""
|
||||||
self.run_installed_binary("ompi_info", [], [f"Ident string: {self.spec.version}", "MCA"])
|
self.run_installed_binary("ompi_info", [], [f"Ident string: {self.spec.version}", "MCA"])
|
||||||
|
|
||||||
def test_version(self):
|
def test_version(self):
|
||||||
|
@ -325,6 +325,7 @@ def check(self):
|
|||||||
@run_after("install")
|
@run_after("install")
|
||||||
@on_package_attributes(run_tests=True)
|
@on_package_attributes(run_tests=True)
|
||||||
def test_install(self):
|
def test_install(self):
|
||||||
|
"""run 'make test_install'"""
|
||||||
# The build script of test_install expects the sources to be copied here:
|
# The build script of test_install expects the sources to be copied here:
|
||||||
install_tree(
|
install_tree(
|
||||||
join_path(self.stage.source_path, "exampleProjects"),
|
join_path(self.stage.source_path, "exampleProjects"),
|
||||||
|
@ -21,4 +21,5 @@ class PyGenshi(PythonPackage):
|
|||||||
depends_on("py-six", type=("build", "run", "test"))
|
depends_on("py-six", type=("build", "run", "test"))
|
||||||
|
|
||||||
def test_testsuite(self):
|
def test_testsuite(self):
|
||||||
|
"""run unittest suite"""
|
||||||
python("-m", "unittest", "-v", "genshi.tests.suite")
|
python("-m", "unittest", "-v", "genshi.tests.suite")
|
||||||
|
@ -137,11 +137,12 @@ def make_qsci_python(self):
|
|||||||
make("install", "-C", "build/")
|
make("install", "-C", "build/")
|
||||||
|
|
||||||
def test_python_import(self):
|
def test_python_import(self):
|
||||||
if "+python" in self.spec:
|
"""check Qsci import"""
|
||||||
python = self.spec["python"].command
|
if self.spec.satisfies("~python"):
|
||||||
if "^py-pyqt5" in self.spec:
|
raise SkipTest("Package must be installed with +python")
|
||||||
python("-c", "import PyQt5.Qsci")
|
|
||||||
if "^py-pyqt6" in self.spec:
|
python = self.spec["python"].command
|
||||||
python("-c", "import PyQt6.Qsci")
|
if "^py-pyqt5" in self.spec:
|
||||||
else:
|
python("-c", "import PyQt5.Qsci")
|
||||||
print("qscintilla ins't built with python, skipping import test")
|
if "^py-pyqt6" in self.spec:
|
||||||
|
python("-c", "import PyQt6.Qsci")
|
||||||
|
Loading…
Reference in New Issue
Block a user