From 1b877e8e0ff0a5457239272b757aadf961bfcc16 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 2 Sep 2019 10:18:14 -0700 Subject: tests and completions for `spack find --json` and `spack find --format` --- lib/spack/spack/cmd/__init__.py | 16 ++-- lib/spack/spack/cmd/find.py | 18 ++-- lib/spack/spack/test/cmd/find.py | 183 ++++++++++++++++++++++++++++++++++++++ lib/spack/spack/test/spec_yaml.py | 14 +++ 4 files changed, 214 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py index f47a4602ce..54496dc32d 100644 --- a/lib/spack/spack/cmd/__init__.py +++ b/lib/spack/spack/cmd/__init__.py @@ -221,8 +221,8 @@ def display_specs_as_json(specs, deps=False): sjson.dump(records, sys.stdout) -def iter_sections(specs, indent, all_headers): - """Break a list of specs into sections indexed by arch/compiler.""" +def iter_groups(specs, indent, all_headers): + """Break a list of specs into groups indexed by arch/compiler.""" # Make a dict with specs keyed by architecture and compiler. index = index_by(specs, ('architecture', 'compiler')) ispace = indent * ' ' @@ -278,9 +278,9 @@ def display_specs(specs, args=None, **kwargs): show_flags (bool): Show compiler flags with specs variants (bool): Show variants with specs 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 - 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 """ @@ -300,7 +300,7 @@ def display_specs(specs, args=None, **kwargs): flags = get_arg('show_flags', False) full_compiler = get_arg('show_full_compiler', False) variants = get_arg('variants', False) - sections = get_arg('sections', True) + groups = get_arg('groups', True) all_headers = get_arg('all_headers', False) decorator = get_arg('decorator', None) @@ -338,7 +338,7 @@ def display_specs(specs, args=None, **kwargs): return string 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 formatted = [] for spec in specs: @@ -367,8 +367,8 @@ def display_specs(specs, args=None, **kwargs): else: print(string) - if sections: - for specs in iter_sections(specs, indent, all_headers): + if groups: + for specs in iter_groups(specs, indent, all_headers): format_list(specs) else: format_list(sorted(specs)) diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py index 6376ef6d88..5d6b07f45f 100644 --- a/lib/spack/spack/cmd/find.py +++ b/lib/spack/spack/cmd/find.py @@ -35,10 +35,10 @@ def setup_parser(subparser): subparser.add_argument('-p', '--paths', action='store_true', help='show paths to package install directories') subparser.add_argument( - '--sections', action='store_true', default=None, dest='sections', - help='group specs in arch/compiler sections (default on)') + '--groups', action='store_true', default=None, dest='groups', + help='display specs in arch/compiler groups (default on)') 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') arguments.add_common_arguments( @@ -177,16 +177,16 @@ def find(parser, args): if env: decorator, added, roots, removed = setup_env(env) - # use sections by default except with format. - if args.sections is None: - args.sections = not args.format + # use groups by default except with format. + if args.groups is None: + 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: msg = "No package matches the query: {0}" msg = msg.format(' '.join(args.constraint)) tty.msg(msg) - return + return 1 # If tags have been specified on the command line, filter by tags if args.tags: @@ -199,7 +199,7 @@ def find(parser, args): else: if env: display_env(env, args, decorator) - if args.sections: + if args.groups: tty.msg("%s" % plural(len(results), 'installed package')) cmd.display_specs( results, args, decorator=decorator, all_headers=True) diff --git a/lib/spack/spack/test/cmd/find.py b/lib/spack/spack/test/cmd/find.py index 7abe63cd4f..c457b8ffac 100644 --- a/lib/spack/spack/test/cmd/find.py +++ b/lib/spack/spack/test/cmd/find.py @@ -4,15 +4,20 @@ # SPDX-License-Identifier: (Apache-2.0 OR MIT) import argparse +import json import pytest +import spack.cmd as cmd import spack.cmd.find from spack.main import SpackCommand +from spack.spec import Spec from spack.util.pattern import Bunch find = SpackCommand('find') +base32_alphabet = 'abcdefghijklmnopqrstuvwxyz234567' + @pytest.fixture(scope='module') def parser(): @@ -111,3 +116,181 @@ def test_namespaces_shown_correctly(database): out = find('--namespace') 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 diff --git a/lib/spack/spack/test/spec_yaml.py b/lib/spack/spack/test/spec_yaml.py index 9943084951..7fd2a36469 100644 --- a/lib/spack/spack/test/spec_yaml.py +++ b/lib/spack/spack/test/spec_yaml.py @@ -104,6 +104,20 @@ def test_using_ordered_dict(mock_packages): 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( config, mock_packages ): -- cgit v1.2.3-60-g2f50