Test package detection in a systematic way (#18175)

This PR adds a new audit sub-command to check that detection of relevant packages
is performed correctly in a few scenarios mocking real use-cases. The data for each 
package being tested is in a YAML file called detection_test.yaml alongside the 
corresponding package.py file.

This is to allow encoding detection tests for compilers and other widely used tools, 
in preparation for compilers as dependencies.
This commit is contained in:
Massimiliano Culpo 2023-09-29 10:24:42 +02:00 committed by GitHub
parent c9ef5c8152
commit 210d221357
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 591 additions and 24 deletions

View file

@ -34,6 +34,7 @@ jobs:
run: | run: |
. share/spack/setup-env.sh . share/spack/setup-env.sh
coverage run $(which spack) audit packages coverage run $(which spack) audit packages
coverage run $(which spack) audit externals
coverage combine coverage combine
coverage xml coverage xml
- name: Package audits (without coverage) - name: Package audits (without coverage)
@ -41,6 +42,7 @@ jobs:
run: | run: |
. share/spack/setup-env.sh . share/spack/setup-env.sh
$(which spack) audit packages $(which spack) audit packages
$(which spack) audit externals
- uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # @v2.1.0 - uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # @v2.1.0
if: ${{ inputs.with_coverage == 'true' }} if: ${{ inputs.with_coverage == 'true' }}
with: with:

View file

@ -6196,7 +6196,100 @@ follows:
"foo-package@{0}".format(version_str) "foo-package@{0}".format(version_str)
) )
.. _package-lifecycle: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add detection tests to packages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To ensure that software is detected correctly for multiple configurations
and on different systems users can write a ``detection_test.yaml`` file and
put it in the package directory alongside the ``package.py`` file.
This YAML file contains enough information for Spack to mock an environment
and try to check if the detection logic yields the results that are expected.
As a general rule, attributes at the top-level of ``detection_test.yaml``
represent search mechanisms and they each map to a list of tests that should confirm
the validity of the package's detection logic.
The detection tests can be run with the following command:
.. code-block:: console
$ spack audit externals
Errors that have been detected are reported to screen.
""""""""""""""""""""""""""
Tests for PATH inspections
""""""""""""""""""""""""""
Detection tests insisting on ``PATH`` inspections are listed under
the ``paths`` attribute:
.. code-block:: yaml
paths:
- layout:
- executables:
- "bin/clang-3.9"
- "bin/clang++-3.9"
script: |
echo "clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)"
echo "Target: x86_64-pc-linux-gnu"
echo "Thread model: posix"
echo "InstalledDir: /usr/bin"
results:
- spec: 'llvm@3.9.1 +clang~lld~lldb'
Each test is performed by first creating a temporary directory structure as
specified in the corresponding ``layout`` and by then running
package detection and checking that the outcome matches the expected
``results``. The exact details on how to specify both the ``layout`` and the
``results`` are reported in the table below:
.. list-table:: Test based on PATH inspections
:header-rows: 1
* - Option Name
- Description
- Allowed Values
- Required Field
* - ``layout``
- Specifies the filesystem tree used for the test
- List of objects
- Yes
* - ``layout:[0]:executables``
- Relative paths for the mock executables to be created
- List of strings
- Yes
* - ``layout:[0]:script``
- Mock logic for the executable
- Any valid shell script
- Yes
* - ``results``
- List of expected results
- List of objects (empty if no result is expected)
- Yes
* - ``results:[0]:spec``
- A spec that is expected from detection
- Any valid spec
- Yes
"""""""""""""""""""""""""""""""
Reuse tests from other packages
"""""""""""""""""""""""""""""""
When using a custom repository, it is possible to customize a package that already exists in ``builtin``
and reuse its external tests. To do so, just write a ``detection_tests.yaml`` alongside the customized
``package.py`` with an ``includes`` attribute. For instance the ``detection_tests.yaml`` for
``myrepo.llvm`` might look like:
.. code-block:: yaml
includes:
- "builtin.llvm"
This YAML file instructs Spack to run the detection tests defined in ``builtin.llvm`` in addition to
those locally defined in the file.
----------------------------- -----------------------------
Style guidelines for packages Style guidelines for packages

View file

@ -38,10 +38,13 @@ def _search_duplicate_compilers(error_cls):
import ast import ast
import collections import collections
import collections.abc import collections.abc
import glob
import inspect import inspect
import itertools import itertools
import pathlib
import pickle import pickle
import re import re
import warnings
from urllib.request import urlopen from urllib.request import urlopen
import llnl.util.lang import llnl.util.lang
@ -798,3 +801,76 @@ def _analyze_variants_in_directive(pkg, constraint, directive, error_cls):
errors.append(err) errors.append(err)
return errors return errors
#: Sanity checks on package directives
external_detection = AuditClass(
group="externals",
tag="PKG-EXTERNALS",
description="Sanity checks for external software detection",
kwargs=("pkgs",),
)
def packages_with_detection_tests():
"""Return the list of packages with a corresponding detection_test.yaml file."""
import spack.config
import spack.util.path
to_be_tested = []
for current_repo in spack.repo.PATH.repos:
namespace = current_repo.namespace
packages_dir = pathlib.PurePath(current_repo.packages_path)
pattern = packages_dir / "**" / "detection_test.yaml"
pkgs_with_tests = [
f"{namespace}.{str(pathlib.PurePath(x).parent.name)}" for x in glob.glob(str(pattern))
]
to_be_tested.extend(pkgs_with_tests)
return to_be_tested
@external_detection
def _test_detection_by_executable(pkgs, error_cls):
"""Test drive external detection for packages"""
import spack.detection
errors = []
# Filter the packages and retain only the ones with detection tests
pkgs_with_tests = packages_with_detection_tests()
selected_pkgs = []
for current_package in pkgs_with_tests:
_, unqualified_name = spack.repo.partition_package_name(current_package)
# Check for both unqualified name and qualified name
if unqualified_name in pkgs or current_package in pkgs:
selected_pkgs.append(current_package)
selected_pkgs.sort()
if not selected_pkgs:
summary = "No detection test to run"
details = [f' "{p}" has no detection test' for p in pkgs]
warnings.warn("\n".join([summary] + details))
return errors
for pkg_name in selected_pkgs:
for idx, test_runner in enumerate(
spack.detection.detection_tests(pkg_name, spack.repo.PATH)
):
specs = test_runner.execute()
expected_specs = test_runner.expected_specs
not_detected = set(expected_specs) - set(specs)
if not_detected:
summary = pkg_name + ": cannot detect some specs"
details = [f'"{s}" was not detected [test_id={idx}]' for s in sorted(not_detected)]
errors.append(error_cls(summary=summary, details=details))
not_expected = set(specs) - set(expected_specs)
if not_expected:
summary = pkg_name + ": detected unexpected specs"
msg = '"{0}" was detected, but was not expected [test_id={1}]'
details = [msg.format(s, idx) for s in sorted(not_expected)]
errors.append(error_cls(summary=summary, details=details))
return errors

View file

@ -3,6 +3,7 @@
# #
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import llnl.util.tty as tty import llnl.util.tty as tty
import llnl.util.tty.colify
import llnl.util.tty.color as cl import llnl.util.tty.color as cl
import spack.audit import spack.audit
@ -20,6 +21,15 @@ def setup_parser(subparser):
# Audit configuration files # Audit configuration files
sp.add_parser("configs", help="audit configuration files") sp.add_parser("configs", help="audit configuration files")
# Audit package recipes
external_parser = sp.add_parser("externals", help="check external detection in packages")
external_parser.add_argument(
"--list",
action="store_true",
dest="list_externals",
help="if passed, list which packages have detection tests",
)
# Https and other linting # Https and other linting
https_parser = sp.add_parser("packages-https", help="check https in packages") https_parser = sp.add_parser("packages-https", help="check https in packages")
https_parser.add_argument( https_parser.add_argument(
@ -29,7 +39,7 @@ def setup_parser(subparser):
# Audit package recipes # Audit package recipes
pkg_parser = sp.add_parser("packages", help="audit package recipes") pkg_parser = sp.add_parser("packages", help="audit package recipes")
for group in [pkg_parser, https_parser]: for group in [pkg_parser, https_parser, external_parser]:
group.add_argument( group.add_argument(
"name", "name",
metavar="PKG", metavar="PKG",
@ -62,6 +72,18 @@ def packages_https(parser, args):
_process_reports(reports) _process_reports(reports)
def externals(parser, args):
if args.list_externals:
msg = "@*{The following packages have detection tests:}"
tty.msg(cl.colorize(msg))
llnl.util.tty.colify.colify(spack.audit.packages_with_detection_tests(), indent=2)
return
pkgs = args.name or spack.repo.PATH.all_package_names()
reports = spack.audit.run_group(args.subcommand, pkgs=pkgs)
_process_reports(reports)
def list(parser, args): def list(parser, args):
for subcommand, check_tags in spack.audit.GROUPS.items(): for subcommand, check_tags in spack.audit.GROUPS.items():
print(cl.colorize("@*b{" + subcommand + "}:")) print(cl.colorize("@*b{" + subcommand + "}:"))
@ -78,6 +100,7 @@ def list(parser, args):
def audit(parser, args): def audit(parser, args):
subcommands = { subcommands = {
"configs": configs, "configs": configs,
"externals": externals,
"packages": packages, "packages": packages,
"packages-https": packages_https, "packages-https": packages_https,
"list": list, "list": list,

View file

@ -5,6 +5,7 @@
import argparse import argparse
import errno import errno
import os import os
import re
import sys import sys
from typing import List, Optional from typing import List, Optional
@ -156,11 +157,20 @@ def packages_to_search_for(
): ):
result = [] result = []
for current_tag in tags: for current_tag in tags:
result.extend(spack.repo.PATH.packages_with_tags(current_tag)) result.extend(spack.repo.PATH.packages_with_tags(current_tag, full=True))
if names: if names:
result = [x for x in result if x in names] # Match both fully qualified and unqualified
parts = [rf"(^{x}$|[.]{x}$)" for x in names]
select_re = re.compile("|".join(parts))
result = [x for x in result if select_re.search(x)]
if exclude: if exclude:
result = [x for x in result if x not in exclude] # Match both fully qualified and unqualified
parts = [rf"(^{x}$|[.]{x}$)" for x in exclude]
select_re = re.compile("|".join(parts))
result = [x for x in result if not select_re.search(x)]
return result return result

View file

@ -4,6 +4,7 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
from .common import DetectedPackage, executable_prefix, update_configuration from .common import DetectedPackage, executable_prefix, update_configuration
from .path import by_path, executables_in_path from .path import by_path, executables_in_path
from .test import detection_tests
__all__ = [ __all__ = [
"DetectedPackage", "DetectedPackage",
@ -11,4 +12,5 @@
"executables_in_path", "executables_in_path",
"executable_prefix", "executable_prefix",
"update_configuration", "update_configuration",
"detection_tests",
] ]

View file

@ -2,7 +2,7 @@
# 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)
"""Detection of software installed in the system based on paths inspections """Detection of software installed in the system, based on paths inspections
and running executables. and running executables.
""" """
import collections import collections
@ -322,12 +322,14 @@ def by_path(
path_hints: Optional[List[str]] = None, path_hints: Optional[List[str]] = None,
max_workers: Optional[int] = None, max_workers: Optional[int] = None,
) -> Dict[str, List[DetectedPackage]]: ) -> Dict[str, List[DetectedPackage]]:
"""Return the list of packages that have been detected on the system, """Return the list of packages that have been detected on the system, keyed by
searching by path. unqualified package name.
Args: Args:
packages_to_search: list of package classes to be detected packages_to_search: list of packages to be detected. Each package can be either unqualified
of fully qualified
path_hints: initial list of paths to be searched path_hints: initial list of paths to be searched
max_workers: maximum number of workers to search for packages in parallel
""" """
# TODO: Packages should be able to define both .libraries and .executables in the future # TODO: Packages should be able to define both .libraries and .executables in the future
# TODO: determine_spec_details should get all relevant libraries and executables in one call # TODO: determine_spec_details should get all relevant libraries and executables in one call
@ -355,7 +357,8 @@ def by_path(
try: try:
detected = future.result(timeout=DETECTION_TIMEOUT) detected = future.result(timeout=DETECTION_TIMEOUT)
if detected: if detected:
result[pkg_name].extend(detected) _, unqualified_name = spack.repo.partition_package_name(pkg_name)
result[unqualified_name].extend(detected)
except Exception: except Exception:
llnl.util.tty.debug( llnl.util.tty.debug(
f"[EXTERNAL DETECTION] Skipping {pkg_name}: timeout reached" f"[EXTERNAL DETECTION] Skipping {pkg_name}: timeout reached"

View file

@ -0,0 +1,187 @@
# Copyright 2013-2023 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)
"""Create and run mock e2e tests for package detection."""
import collections
import contextlib
import pathlib
import tempfile
from typing import Any, Deque, Dict, Generator, List, NamedTuple, Tuple
import jinja2
from llnl.util import filesystem
import spack.repo
import spack.spec
from spack.util import spack_yaml
from .path import by_path
class MockExecutables(NamedTuple):
"""Mock executables to be used in detection tests"""
#: Relative paths for mock executables to be created
executables: List[str]
#: Shell script for the mock executable
script: str
class ExpectedTestResult(NamedTuple):
"""Data structure to model assertions on detection tests"""
#: Spec to be detected
spec: str
class DetectionTest(NamedTuple):
"""Data structure to construct detection tests by PATH inspection.
Packages may have a YAML file containing the description of one or more detection tests
to be performed. Each test creates a few mock executable scripts in a temporary folder,
and checks that detection by PATH gives the expected results.
"""
pkg_name: str
layout: List[MockExecutables]
results: List[ExpectedTestResult]
class Runner:
"""Runs an external detection test"""
def __init__(self, *, test: DetectionTest, repository: spack.repo.RepoPath) -> None:
self.test = test
self.repository = repository
self.tmpdir = tempfile.TemporaryDirectory()
def execute(self) -> List[spack.spec.Spec]:
"""Executes a test and returns the specs that have been detected.
This function sets-up a test in a temporary directory, according to the prescriptions
in the test layout, then performs a detection by executables and returns the specs that
have been detected.
"""
with self._mock_layout() as path_hints:
entries = by_path([self.test.pkg_name], path_hints=path_hints)
_, unqualified_name = spack.repo.partition_package_name(self.test.pkg_name)
specs = set(x.spec for x in entries[unqualified_name])
return list(specs)
@contextlib.contextmanager
def _mock_layout(self) -> Generator[List[str], None, None]:
hints = set()
try:
for entry in self.test.layout:
exes = self._create_executable_scripts(entry)
for mock_executable in exes:
hints.add(str(mock_executable.parent))
yield list(hints)
finally:
self.tmpdir.cleanup()
def _create_executable_scripts(self, mock_executables: MockExecutables) -> List[pathlib.Path]:
relative_paths = mock_executables.executables
script = mock_executables.script
script_template = jinja2.Template("#!/bin/bash\n{{ script }}\n")
result = []
for mock_exe_path in relative_paths:
rel_path = pathlib.Path(mock_exe_path)
abs_path = pathlib.Path(self.tmpdir.name) / rel_path
abs_path.parent.mkdir(parents=True, exist_ok=True)
abs_path.write_text(script_template.render(script=script))
filesystem.set_executable(abs_path)
result.append(abs_path)
return result
@property
def expected_specs(self) -> List[spack.spec.Spec]:
return [spack.spec.Spec(r.spec) for r in self.test.results]
def detection_tests(pkg_name: str, repository: spack.repo.RepoPath) -> List[Runner]:
"""Returns a list of test runners for a given package.
Currently, detection tests are specified in a YAML file, called ``detection_test.yaml``,
alongside the ``package.py`` file.
This function reads that file to create a bunch of ``Runner`` objects.
Args:
pkg_name: name of the package to test
repository: repository where the package lives
"""
result = []
detection_tests_content = read_detection_tests(pkg_name, repository)
tests_by_path = detection_tests_content.get("paths", [])
for single_test_data in tests_by_path:
mock_executables = []
for layout in single_test_data["layout"]:
mock_executables.append(
MockExecutables(executables=layout["executables"], script=layout["script"])
)
expected_results = []
for assertion in single_test_data["results"]:
expected_results.append(ExpectedTestResult(spec=assertion["spec"]))
current_test = DetectionTest(
pkg_name=pkg_name, layout=mock_executables, results=expected_results
)
result.append(Runner(test=current_test, repository=repository))
return result
def read_detection_tests(pkg_name: str, repository: spack.repo.RepoPath) -> Dict[str, Any]:
"""Returns the normalized content of the detection_tests.yaml associated with the package
passed in input.
The content is merged with that of any package that is transitively included using the
"includes" attribute.
Args:
pkg_name: name of the package to test
repository: repository in which to search for packages
"""
content_stack, seen = [], set()
included_packages: Deque[str] = collections.deque()
root_detection_yaml, result = _detection_tests_yaml(pkg_name, repository)
included_packages.extend(result.get("includes", []))
seen |= set(result.get("includes", []))
while included_packages:
current_package = included_packages.popleft()
try:
current_detection_yaml, content = _detection_tests_yaml(current_package, repository)
except FileNotFoundError as e:
msg = (
f"cannot read the detection tests from the '{current_package}' package, "
f"included by {root_detection_yaml}"
)
raise FileNotFoundError(msg + f"\n\n\t{e}\n")
content_stack.append((current_package, content))
included_packages.extend(x for x in content.get("includes", []) if x not in seen)
seen |= set(content.get("includes", []))
result.setdefault("paths", [])
for pkg_name, content in content_stack:
result["paths"].extend(content.get("paths", []))
return result
def _detection_tests_yaml(
pkg_name: str, repository: spack.repo.RepoPath
) -> Tuple[pathlib.Path, Dict[str, Any]]:
pkg_dir = pathlib.Path(repository.filename_for_package_name(pkg_name)).parent
detection_tests_yaml = pkg_dir / "detection_test.yaml"
with open(str(detection_tests_yaml)) as f:
content = spack_yaml.load(f)
return detection_tests_yaml, content

View file

@ -24,7 +24,7 @@
import traceback import traceback
import types import types
import uuid import uuid
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Tuple, Union
import llnl.path import llnl.path
import llnl.util.filesystem as fs import llnl.util.filesystem as fs
@ -745,10 +745,18 @@ def all_package_paths(self):
for name in self.all_package_names(): for name in self.all_package_names():
yield self.package_path(name) yield self.package_path(name)
def packages_with_tags(self, *tags): def packages_with_tags(self, *tags, full=False):
"""Returns a list of packages matching any of the tags in input.
Args:
full: if True the package names in the output are fully-qualified
"""
r = set() r = set()
for repo in self.repos: for repo in self.repos:
r |= set(repo.packages_with_tags(*tags)) current = repo.packages_with_tags(*tags)
if full:
current = [f"{repo.namespace}.{x}" for x in current]
r |= set(current)
return sorted(r) return sorted(r)
def all_package_classes(self): def all_package_classes(self):
@ -1124,7 +1132,8 @@ def extensions_for(self, extendee_spec):
def dirname_for_package_name(self, pkg_name): def dirname_for_package_name(self, pkg_name):
"""Get the directory name for a particular package. This is the """Get the directory name for a particular package. This is the
directory that contains its package.py file.""" directory that contains its package.py file."""
return os.path.join(self.packages_path, pkg_name) _, unqualified_name = self.partition_package_name(pkg_name)
return os.path.join(self.packages_path, unqualified_name)
def filename_for_package_name(self, pkg_name): def filename_for_package_name(self, pkg_name):
"""Get the filename for the module we should load for a particular """Get the filename for the module we should load for a particular
@ -1222,15 +1231,10 @@ def get_pkg_class(self, pkg_name):
package. Then extracts the package class from the module package. Then extracts the package class from the module
according to Spack's naming convention. according to Spack's naming convention.
""" """
namespace, _, pkg_name = pkg_name.rpartition(".") namespace, pkg_name = self.partition_package_name(pkg_name)
if namespace and (namespace != self.namespace):
raise InvalidNamespaceError(
"Invalid namespace for %s repo: %s" % (self.namespace, namespace)
)
class_name = nm.mod_to_class(pkg_name) class_name = nm.mod_to_class(pkg_name)
fullname = f"{self.full_namespace}.{pkg_name}"
fullname = "{0}.{1}".format(self.full_namespace, pkg_name)
try: try:
module = importlib.import_module(fullname) module = importlib.import_module(fullname)
except ImportError: except ImportError:
@ -1241,7 +1245,7 @@ def get_pkg_class(self, pkg_name):
cls = getattr(module, class_name) cls = getattr(module, class_name)
if not inspect.isclass(cls): if not inspect.isclass(cls):
tty.die("%s.%s is not a class" % (pkg_name, class_name)) tty.die(f"{pkg_name}.{class_name} is not a class")
new_cfg_settings = ( new_cfg_settings = (
spack.config.get("packages").get(pkg_name, {}).get("package_attributes", {}) spack.config.get("packages").get(pkg_name, {}).get("package_attributes", {})
@ -1280,6 +1284,15 @@ def get_pkg_class(self, pkg_name):
return cls return cls
def partition_package_name(self, pkg_name: str) -> Tuple[str, str]:
namespace, pkg_name = partition_package_name(pkg_name)
if namespace and (namespace != self.namespace):
raise InvalidNamespaceError(
f"Invalid namespace for the '{self.namespace}' repo: {namespace}"
)
return namespace, pkg_name
def __str__(self): def __str__(self):
return "[Repo '%s' at '%s']" % (self.namespace, self.root) return "[Repo '%s' at '%s']" % (self.namespace, self.root)
@ -1293,6 +1306,20 @@ def __contains__(self, pkg_name):
RepoType = Union[Repo, RepoPath] RepoType = Union[Repo, RepoPath]
def partition_package_name(pkg_name: str) -> Tuple[str, str]:
"""Given a package name that might be fully-qualified, returns the namespace part,
if present and the unqualified package name.
If the package name is unqualified, the namespace is an empty string.
Args:
pkg_name: a package name, either unqualified like "llvl", or
fully-qualified, like "builtin.llvm"
"""
namespace, _, pkg_name = pkg_name.rpartition(".")
return namespace, pkg_name
def create_repo(root, namespace=None, subdir=packages_dir_name): def create_repo(root, namespace=None, subdir=packages_dir_name):
"""Create a new repository in root with the specified namespace. """Create a new repository in root with the specified namespace.

View file

@ -120,8 +120,9 @@ def test_find_external_cmd_not_buildable(mutable_config, working_env, mock_execu
"names,tags,exclude,expected", "names,tags,exclude,expected",
[ [
# find --all # find --all
(None, ["detectable"], [], ["find-externals1"]), (None, ["detectable"], [], ["builtin.mock.find-externals1"]),
# find --all --exclude find-externals1 # find --all --exclude find-externals1
(None, ["detectable"], ["builtin.mock.find-externals1"], []),
(None, ["detectable"], ["find-externals1"], []), (None, ["detectable"], ["find-externals1"], []),
# find cmake (and cmake is not detectable) # find cmake (and cmake is not detectable)
(["cmake"], ["detectable"], [], []), (["cmake"], ["detectable"], [], []),

View file

@ -181,3 +181,15 @@ def test_repository_construction_doesnt_use_globals(nullify_globals, repo_paths,
repo_path = spack.repo.RepoPath(*repo_paths) repo_path = spack.repo.RepoPath(*repo_paths)
assert len(repo_path.repos) == len(namespaces) assert len(repo_path.repos) == len(namespaces)
assert [x.namespace for x in repo_path.repos] == namespaces assert [x.namespace for x in repo_path.repos] == namespaces
@pytest.mark.parametrize("method_name", ["dirname_for_package_name", "filename_for_package_name"])
def test_path_computation_with_names(method_name, mock_repo_path):
"""Tests that repositories can compute the correct paths when using both fully qualified
names and unqualified names.
"""
repo_path = spack.repo.RepoPath(mock_repo_path)
method = getattr(repo_path, method_name)
unqualified = method("mpileaks")
qualified = method("builtin.mock.mpileaks")
assert qualified == unqualified

View file

@ -423,7 +423,7 @@ _spack_audit() {
then then
SPACK_COMPREPLY="-h --help" SPACK_COMPREPLY="-h --help"
else else
SPACK_COMPREPLY="configs packages-https packages list" SPACK_COMPREPLY="configs externals packages-https packages list"
fi fi
} }
@ -431,6 +431,15 @@ _spack_audit_configs() {
SPACK_COMPREPLY="-h --help" SPACK_COMPREPLY="-h --help"
} }
_spack_audit_externals() {
if $list_options
then
SPACK_COMPREPLY="-h --help --list"
else
SPACK_COMPREPLY=""
fi
}
_spack_audit_packages_https() { _spack_audit_packages_https() {
if $list_options if $list_options
then then

View file

@ -508,6 +508,7 @@ complete -c spack -n '__fish_spack_using_command arch' -s b -l backend -d 'print
# spack audit # spack audit
set -g __fish_spack_optspecs_spack_audit h/help set -g __fish_spack_optspecs_spack_audit h/help
complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a configs -d 'audit configuration files' complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a configs -d 'audit configuration files'
complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a externals -d 'check external detection in packages'
complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a packages-https -d 'check https in packages' complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a packages-https -d 'check https in packages'
complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a packages -d 'audit package recipes' complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a packages -d 'audit package recipes'
complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a list -d 'list available checks and exits' complete -c spack -n '__fish_spack_using_command_pos 0 audit' -f -a list -d 'list available checks and exits'
@ -519,6 +520,14 @@ set -g __fish_spack_optspecs_spack_audit_configs h/help
complete -c spack -n '__fish_spack_using_command audit configs' -s h -l help -f -a help complete -c spack -n '__fish_spack_using_command audit configs' -s h -l help -f -a help
complete -c spack -n '__fish_spack_using_command audit configs' -s h -l help -d 'show this help message and exit' complete -c spack -n '__fish_spack_using_command audit configs' -s h -l help -d 'show this help message and exit'
# spack audit externals
set -g __fish_spack_optspecs_spack_audit_externals h/help list
complete -c spack -n '__fish_spack_using_command_pos_remainder 0 audit externals' -f -a '(__fish_spack_packages)'
complete -c spack -n '__fish_spack_using_command audit externals' -s h -l help -f -a help
complete -c spack -n '__fish_spack_using_command audit externals' -s h -l help -d 'show this help message and exit'
complete -c spack -n '__fish_spack_using_command audit externals' -l list -f -a list_externals
complete -c spack -n '__fish_spack_using_command audit externals' -l list -d 'if passed, list which packages have detection tests'
# spack audit packages-https # spack audit packages-https
set -g __fish_spack_optspecs_spack_audit_packages_https h/help all set -g __fish_spack_optspecs_spack_audit_packages_https h/help all
complete -c spack -n '__fish_spack_using_command_pos_remainder 0 audit packages-https' -f -a '(__fish_spack_packages)' complete -c spack -n '__fish_spack_using_command_pos_remainder 0 audit packages-https' -f -a '(__fish_spack_packages)'

View file

@ -0,0 +1,38 @@
paths:
# Ubuntu 18.04, system compilers without Fortran
- layout:
- executables:
- "bin/gcc"
- "bin/g++"
script: "echo 7.5.0"
results:
- spec: "gcc@7.5.0 languages=c,c++"
# Mock a version < 7 of GCC that requires -dumpversion and
# errors with -dumpfullversion
- layout:
- executables:
- "bin/gcc-5"
- "bin/g++-5"
- "bin/gfortran-5"
script: |
if [[ "$1" == "-dumpversion" ]] ; then
echo "5.5.0"
else
echo "gcc-5: fatal error: no input files"
echo "compilation terminated."
exit 1
fi
results:
- spec: "gcc@5.5.0 languages=c,c++,fortran"
# Multiple compilers present at the same time
- layout:
- executables:
- "bin/x86_64-linux-gnu-gcc-6"
script: 'echo 6.5.0'
- executables:
- "bin/x86_64-linux-gnu-gcc-10"
- "bin/x86_64-linux-gnu-g++-10"
script: "echo 10.1.0"
results:
- spec: "gcc@6.5.0 languages=c"
- spec: "gcc@10.1.0 languages=c,c++"

View file

@ -0,0 +1,19 @@
paths:
- layout:
- executables:
- "bin/intel64/icc"
script: |
echo "icc (ICC) 18.0.5 20180823"
echo "Copyright (C) 1985-2018 Intel Corporation. All rights reserved."
- executables:
- "bin/intel64/icpc"
script: |
echo "icpc (ICC) 18.0.5 20180823"
echo "Copyright (C) 1985-2018 Intel Corporation. All rights reserved."
- executables:
- "bin/intel64/ifort"
script: |
echo "ifort (IFORT) 18.0.5 20180823"
echo "Copyright (C) 1985-2018 Intel Corporation. All rights reserved."
results:
- spec: 'intel@18.0.5'

View file

@ -0,0 +1,56 @@
paths:
- layout:
- executables:
- "bin/clang-3.9"
script: |
echo "clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)"
echo "Target: x86_64-pc-linux-gnu"
echo "Thread model: posix"
echo "InstalledDir: /usr/bin"
- executables:
- "bin/clang++-3.9"
script: |
echo "clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)"
echo "Target: x86_64-pc-linux-gnu"
echo "Thread model: posix"
echo "InstalledDir: /usr/bin"
results:
- spec: 'llvm@3.9.1 +clang~lld~lldb'
# Multiple LLVM packages in the same prefix
- layout:
- executables:
- "bin/clang-8"
- "bin/clang++-8"
script: |
echo "clang version 8.0.0-3~ubuntu18.04.2 (tags/RELEASE_800/final)"
echo "Target: x86_64-pc-linux-gnu"
echo "Thread model: posix"
echo "InstalledDir: /usr/bin"
- executables:
- "bin/ld.lld-8"
script: 'echo "LLD 8.0.0 (compatible with GNU linkers)"'
- executables:
- "bin/lldb"
script: 'echo "lldb version 8.0.0"'
- executables:
- "bin/clang-3.9"
- "bin/clang++-3.9"
script: |
echo "clang version 3.9.1-19ubuntu1 (tags/RELEASE_391/rc2)"
echo "Target: x86_64-pc-linux-gnu"
echo "Thread model: posix"
echo "InstalledDir: /usr/bin"
results:
- spec: 'llvm@8.0.0+clang+lld+lldb'
- spec: 'llvm@3.9.1+clang~lld~lldb'
# Apple Clang should not be detected
- layout:
- executables:
- "bin/clang"
- "bin/clang++"
script: |
echo "Apple clang version 11.0.0 (clang-1100.0.33.8)"
echo "Target: x86_64-apple-darwin19.5.0"
echo "Thread model: posix"
echo "InstalledDir: /Library/Developer/CommandLineTools/usr/bin"
results: []