tests and completions for spack find --json and spack find --format

This commit is contained in:
Todd Gamblin 2019-09-02 10:18:14 -07:00
parent 2dc7467760
commit 1b877e8e0f
5 changed files with 217 additions and 19 deletions

View File

@ -221,8 +221,8 @@ def display_specs_as_json(specs, deps=False):
sjson.dump(records, sys.stdout) sjson.dump(records, sys.stdout)
def iter_sections(specs, indent, all_headers): def iter_groups(specs, indent, all_headers):
"""Break a list of specs into sections indexed by arch/compiler.""" """Break a list of specs into groups indexed by arch/compiler."""
# Make a dict with specs keyed by architecture and compiler. # Make a dict with specs keyed by architecture and compiler.
index = index_by(specs, ('architecture', 'compiler')) index = index_by(specs, ('architecture', 'compiler'))
ispace = indent * ' ' ispace = indent * ' '
@ -278,9 +278,9 @@ def display_specs(specs, args=None, **kwargs):
show_flags (bool): Show compiler flags with specs show_flags (bool): Show compiler flags with specs
variants (bool): Show variants with specs variants (bool): Show variants with specs
indent (int): indent each line this much indent (int): indent each line this much
sections (bool): display specs grouped by arch/compiler (default True) groups (bool): display specs grouped by arch/compiler (default True)
decorators (dict): dictionary mappng specs to decorators decorators (dict): dictionary mappng specs to decorators
header_callback (function): called at start of arch/compiler sections header_callback (function): called at start of arch/compiler groups
all_headers (bool): show headers even when arch/compiler aren't defined all_headers (bool): show headers even when arch/compiler aren't defined
""" """
@ -300,7 +300,7 @@ def get_arg(name, default=None):
flags = get_arg('show_flags', False) flags = get_arg('show_flags', False)
full_compiler = get_arg('show_full_compiler', False) full_compiler = get_arg('show_full_compiler', False)
variants = get_arg('variants', False) variants = get_arg('variants', False)
sections = get_arg('sections', True) groups = get_arg('groups', True)
all_headers = get_arg('all_headers', False) all_headers = get_arg('all_headers', False)
decorator = get_arg('decorator', None) decorator = get_arg('decorator', None)
@ -338,7 +338,7 @@ def fmt(s, depth=0):
return string return string
def format_list(specs): def format_list(specs):
"""Display a single list of specs, with no sections""" """Display a single list of specs, with no groups"""
# create the final, formatted versions of all specs # create the final, formatted versions of all specs
formatted = [] formatted = []
for spec in specs: for spec in specs:
@ -367,8 +367,8 @@ def format_list(specs):
else: else:
print(string) print(string)
if sections: if groups:
for specs in iter_sections(specs, indent, all_headers): for specs in iter_groups(specs, indent, all_headers):
format_list(specs) format_list(specs)
else: else:
format_list(sorted(specs)) format_list(sorted(specs))

View File

@ -35,10 +35,10 @@ def setup_parser(subparser):
subparser.add_argument('-p', '--paths', action='store_true', subparser.add_argument('-p', '--paths', action='store_true',
help='show paths to package install directories') help='show paths to package install directories')
subparser.add_argument( subparser.add_argument(
'--sections', action='store_true', default=None, dest='sections', '--groups', action='store_true', default=None, dest='groups',
help='group specs in arch/compiler sections (default on)') help='display specs in arch/compiler groups (default on)')
subparser.add_argument( subparser.add_argument(
'--no-sections', action='store_false', default=None, dest='sections', '--no-groups', action='store_false', default=None, dest='groups',
help='do not group specs by arch/compiler') help='do not group specs by arch/compiler')
arguments.add_common_arguments( arguments.add_common_arguments(
@ -177,16 +177,16 @@ def find(parser, args):
if env: if env:
decorator, added, roots, removed = setup_env(env) decorator, added, roots, removed = setup_env(env)
# use sections by default except with format. # use groups by default except with format.
if args.sections is None: if args.groups is None:
args.sections = not args.format args.groups = not args.format
# Exit early if no package matches the constraint # Exit early with an error code if no package matches the constraint
if not results and args.constraint: if not results and args.constraint:
msg = "No package matches the query: {0}" msg = "No package matches the query: {0}"
msg = msg.format(' '.join(args.constraint)) msg = msg.format(' '.join(args.constraint))
tty.msg(msg) tty.msg(msg)
return return 1
# If tags have been specified on the command line, filter by tags # If tags have been specified on the command line, filter by tags
if args.tags: if args.tags:
@ -199,7 +199,7 @@ def find(parser, args):
else: else:
if env: if env:
display_env(env, args, decorator) display_env(env, args, decorator)
if args.sections: if args.groups:
tty.msg("%s" % plural(len(results), 'installed package')) tty.msg("%s" % plural(len(results), 'installed package'))
cmd.display_specs( cmd.display_specs(
results, args, decorator=decorator, all_headers=True) results, args, decorator=decorator, all_headers=True)

View File

@ -4,15 +4,20 @@
# SPDX-License-Identifier: (Apache-2.0 OR MIT) # SPDX-License-Identifier: (Apache-2.0 OR MIT)
import argparse import argparse
import json
import pytest import pytest
import spack.cmd as cmd
import spack.cmd.find import spack.cmd.find
from spack.main import SpackCommand from spack.main import SpackCommand
from spack.spec import Spec
from spack.util.pattern import Bunch from spack.util.pattern import Bunch
find = SpackCommand('find') find = SpackCommand('find')
base32_alphabet = 'abcdefghijklmnopqrstuvwxyz234567'
@pytest.fixture(scope='module') @pytest.fixture(scope='module')
def parser(): def parser():
@ -111,3 +116,181 @@ def test_namespaces_shown_correctly(database):
out = find('--namespace') out = find('--namespace')
assert 'builtin.mock.zmpi' in out assert 'builtin.mock.zmpi' in out
def _check_json_output(spec_list):
assert len(spec_list) == 3
assert all(spec["name"] == "mpileaks" for spec in spec_list)
deps = [spec["dependencies"] for spec in spec_list]
assert sum(["zmpi" in d for d in deps]) == 1
assert sum(["mpich" in d for d in deps]) == 1
assert sum(["mpich2" in d for d in deps]) == 1
def _check_json_output_deps(spec_list):
assert len(spec_list) == 13
names = [spec["name"] for spec in spec_list]
assert names.count("mpileaks") == 3
assert names.count("callpath") == 3
assert names.count("zmpi") == 1
assert names.count("mpich") == 1
assert names.count("mpich2") == 1
assert names.count("fake") == 1
assert names.count("dyninst") == 1
assert names.count("libdwarf") == 1
assert names.count("libelf") == 1
@pytest.mark.db
def test_find_json(database):
output = find('--json', 'mpileaks')
spec_list = json.loads(output)
_check_json_output(spec_list)
@pytest.mark.db
def test_find_json_deps(database):
output = find('-d', '--json', 'mpileaks')
spec_list = json.loads(output)
_check_json_output_deps(spec_list)
@pytest.mark.db
def test_display_json(database, capsys):
specs = [Spec(s).concretized() for s in [
"mpileaks ^zmpi",
"mpileaks ^mpich",
"mpileaks ^mpich2",
]]
cmd.display_specs_as_json(specs)
spec_list = json.loads(capsys.readouterr()[0])
_check_json_output(spec_list)
cmd.display_specs_as_json(specs + specs + specs)
spec_list = json.loads(capsys.readouterr()[0])
_check_json_output(spec_list)
@pytest.mark.db
def test_display_json_deps(database, capsys):
specs = [Spec(s).concretized() for s in [
"mpileaks ^zmpi",
"mpileaks ^mpich",
"mpileaks ^mpich2",
]]
cmd.display_specs_as_json(specs, deps=True)
spec_list = json.loads(capsys.readouterr()[0])
_check_json_output_deps(spec_list)
cmd.display_specs_as_json(specs + specs + specs, deps=True)
spec_list = json.loads(capsys.readouterr()[0])
_check_json_output_deps(spec_list)
@pytest.mark.db
def test_find_format(database, config):
output = find('--format', '{name}-{^mpi.name}', 'mpileaks')
assert set(output.strip().split('\n')) == set([
"mpileaks-zmpi",
"mpileaks-mpich",
"mpileaks-mpich2",
])
output = find('--format', '{name}-{version}-{compiler.name}-{^mpi.name}',
'mpileaks')
assert set(output.strip().split('\n')) == set([
"mpileaks-2.3-gcc-zmpi",
"mpileaks-2.3-gcc-mpich",
"mpileaks-2.3-gcc-mpich2",
])
output = find('--format', '{name}-{^mpi.name}-{hash:7}',
'mpileaks')
elements = output.strip().split('\n')
assert set(e[:-7] for e in elements) == set([
"mpileaks-zmpi-",
"mpileaks-mpich-",
"mpileaks-mpich2-",
])
# hashes are in base32
for e in elements:
for c in e[-7:]:
assert c in base32_alphabet
@pytest.mark.db
def test_find_format_deps(database, config):
output = find('-d', '--format', '{name}-{version}', 'mpileaks', '^zmpi')
assert output == """\
mpileaks-2.3
callpath-1.0
dyninst-8.2
libdwarf-20130729
libelf-0.8.13
zmpi-1.0
fake-1.0
"""
@pytest.mark.db
def test_find_format_deps_paths(database, config):
output = find('-dp', '--format', '{name}-{version}', 'mpileaks', '^zmpi')
spec = Spec("mpileaks ^zmpi").concretized()
prefixes = [s.prefix for s in spec.traverse()]
assert output == """\
mpileaks-2.3 {0}
callpath-1.0 {1}
dyninst-8.2 {2}
libdwarf-20130729 {3}
libelf-0.8.13 {4}
zmpi-1.0 {5}
fake-1.0 {6}
""".format(*prefixes)
@pytest.mark.db
def test_find_very_long(database, config):
output = find('-L', '--no-groups', "mpileaks")
specs = [Spec(s).concretized() for s in [
"mpileaks ^zmpi",
"mpileaks ^mpich",
"mpileaks ^mpich2",
]]
assert set(output.strip().split("\n")) == set([
("%s mpileaks@2.3" % s.dag_hash()) for s in specs
])
@pytest.mark.db
def test_find_show_compiler(database, config):
output = find('--no-groups', '--show-full-compiler', "mpileaks")
assert "mpileaks@2.3%gcc@4.5.0" in output
@pytest.mark.db
def test_find_not_found(database, config, capsys):
with capsys.disabled():
output = find("foobarbaz", fail_on_error=False)
assert "No package matches the query: foobarbaz" in output
assert find.returncode == 1
@pytest.mark.db
def test_find_no_sections(database, config):
output = find()
assert "-----------" in output
output = find("--no-groups")
assert "-----------" not in output
assert "==>" not in output

View File

@ -104,6 +104,20 @@ def descend_and_check(iterable, level=0):
assert level >= 5 assert level >= 5
def test_to_record_dict(mock_packages, config):
specs = ['mpileaks', 'zmpi', 'dttop']
for name in specs:
spec = Spec(name).concretized()
record = spec.to_record_dict()
assert record["name"] == name
assert "hash" in record
node = spec.to_node_dict()
for key, value in node[name].items():
assert key in record
assert record[key] == value
def test_ordered_read_not_required_for_consistent_dag_hash( def test_ordered_read_not_required_for_consistent_dag_hash(
config, mock_packages config, mock_packages
): ):

View File

@ -521,7 +521,8 @@ function _spack_fetch {
function _spack_find { function _spack_find {
if $list_options if $list_options
then then
compgen -W "-h --help -s --short -p --paths -d --deps -l --long compgen -W "-h --help -s --short -d --deps -p --paths
--format --json --groups --no-groups -l --long
-L --very-long -t --tags -c --show-concretized -L --very-long -t --tags -c --show-concretized
-f --show-flags --show-full-compiler -x --explicit -f --show-flags --show-full-compiler -x --explicit
-X --implicit -u --unknown -m --missing -v --variants -X --implicit -u --unknown -m --missing -v --variants
@ -1282,7 +1283,7 @@ function _all_resource_hashes {
} }
function _installed_packages { function _installed_packages {
spack --color=never find | grep -v "^--" spack --color=never find --no-groups
} }
function _installed_compilers { function _installed_compilers {