From 3cb40e1c02186c1f662c4f28db0b1527fff24360 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 22 Jul 2019 17:02:21 -0700 Subject: commands: add tests for all subcommands of `spack pkg` --- lib/spack/spack/cmd/pkg.py | 63 +++++++---- lib/spack/spack/repo.py | 3 +- lib/spack/spack/test/cmd/pkg.py | 229 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+), 24 deletions(-) create mode 100644 lib/spack/spack/test/cmd/pkg.py (limited to 'lib') diff --git a/lib/spack/spack/cmd/pkg.py b/lib/spack/spack/cmd/pkg.py index 0164f2e612..b81641febc 100644 --- a/lib/spack/spack/cmd/pkg.py +++ b/lib/spack/spack/cmd/pkg.py @@ -7,15 +7,15 @@ from __future__ import print_function import os import argparse +import re import llnl.util.tty as tty from llnl.util.tty.colify import colify -from llnl.util.filesystem import working_dir +import spack.cmd import spack.paths import spack.repo from spack.util.executable import which -from spack.cmd import spack_is_git_repo description = "query packages associated with particular git revisions" section = "developer" @@ -57,6 +57,10 @@ def setup_parser(subparser): add_parser.add_argument( 'rev2', nargs='?', default='HEAD', help="revision to compare to rev1 (default is HEAD)") + add_parser.add_argument( + '-t', '--type', action='store', default='C', + help="Types of changes to show (A: added, R: removed, " + "C: changed); default is 'C'") rm_parser = sp.add_parser('removed', help=pkg_removed.__doc__) rm_parser.add_argument( @@ -84,20 +88,20 @@ def get_git(): def list_packages(rev): - pkgpath = packages_path() - relpath = pkgpath[len(spack.paths.prefix + os.path.sep):] + os.path.sep - git = get_git() - with working_dir(spack.paths.prefix): - output = git('ls-tree', '--full-tree', '--name-only', rev, relpath, - output=str) - return sorted(line[len(relpath):] for line in output.split('\n') if line) + + # git ls-tree does not support ... merge-base syntax, so do it manually + if rev.endswith('...'): + ref = rev.replace('...', '') + rev = git('merge-base', ref, 'HEAD', output=str).strip() + + output = git('ls-tree', '--name-only', rev, output=str) + return sorted(line for line in output.split('\n') + if line and not line.startswith('.')) def pkg_add(args): """add a package to the git stage with `git add`""" - pkgpath = packages_path() - for pkg_name in args.packages: filename = spack.repo.path.filename_for_package_name(pkg_name) if not os.path.isfile(filename): @@ -105,8 +109,7 @@ def pkg_add(args): pkg_name, filename) git = get_git() - with working_dir(spack.paths.prefix): - git('-C', pkgpath, 'add', filename) + git('add', filename) def pkg_list(args): @@ -151,24 +154,38 @@ def pkg_added(args): def pkg_changed(args): """show packages changed since a commit""" - pkgpath = spack.repo.path.get_repo('builtin').packages_path - rel_pkg_path = os.path.relpath(pkgpath, spack.paths.prefix) + lower_type = args.type.lower() + if not re.match('^[arc]*$', lower_type): + tty.die("Invald change type: '%s'." % args.type, + "Can contain only A (added), R (removed), or C (changed)") + + removed, added = diff_packages(args.rev1, args.rev2) git = get_git() - paths = git('diff', '--name-only', args.rev1, args.rev2, pkgpath, - output=str).strip().split('\n') + out = git('diff', '--relative', '--name-only', args.rev1, args.rev2, + output=str).strip() - packages = set([]) - for path in paths: - path = path.replace(rel_pkg_path + os.sep, '') + lines = [] if not out else re.split(r'\s+', out) + changed = set() + for path in lines: pkg_name, _, _ = path.partition(os.sep) - packages.add(pkg_name) + if pkg_name not in added and pkg_name not in removed: + changed.add(pkg_name) + + packages = set() + if 'a' in lower_type: + packages |= added + if 'r' in lower_type: + packages |= removed + if 'c' in lower_type: + packages |= changed - colify(sorted(packages)) + if packages: + colify(sorted(packages)) def pkg(parser, args): - if not spack_is_git_repo(): + if not spack.cmd.spack_is_git_repo(): tty.die("This spack is not a git clone. Can't use 'spack pkg'") action = {'add': pkg_add, diff --git a/lib/spack/spack/repo.py b/lib/spack/spack/repo.py index 1e593634b9..f9f733ef7f 100644 --- a/lib/spack/spack/repo.py +++ b/lib/spack/spack/repo.py @@ -150,7 +150,8 @@ class FastPackageChecker(Mapping): pkg_dir = os.path.join(self.packages_path, pkg_name) # Warn about invalid names that look like packages. - if not valid_module_name(pkg_name): + if (not valid_module_name(pkg_name) + and not pkg_name.startswith('.')): msg = 'Skipping package at {0}. ' msg += '"{1}" is not a valid Spack module name.' tty.warn(msg.format(pkg_dir, pkg_name)) diff --git a/lib/spack/spack/test/cmd/pkg.py b/lib/spack/spack/test/cmd/pkg.py new file mode 100644 index 0000000000..c9e8e74c4a --- /dev/null +++ b/lib/spack/spack/test/cmd/pkg.py @@ -0,0 +1,229 @@ +# Copyright 2013-2019 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 __future__ import print_function + +import pytest +import re +import shutil + +from llnl.util.filesystem import mkdirp, working_dir + +import spack.main +import spack.cmd.pkg +from spack.util.executable import which + +pytestmark = pytest.mark.skipif(not which('git'), + reason="spack pkg tests require git") + +#: new fake package template +pkg_template = '''\ +from spack import * + +class {name}(Package): + homepage = "http://www.example.com" + url = "http://www.example.com/test-1.0.tar.gz" + + version('1.0', '0123456789abcdef0123456789abcdef') + + def install(self, spec, prefix): + pass +''' + +abc = set(('pkg-a', 'pkg-b', 'pkg-c')) +abd = set(('pkg-a', 'pkg-b', 'pkg-d')) + + +# Force all tests to use a git repository *in* the mock packages repo. +@pytest.fixture(scope='module') +def mock_pkg_git_repo(tmpdir_factory): + """Copy the builtin.mock repo and make a mutable git repo inside it.""" + tmproot = tmpdir_factory.mktemp('mock_pkg_git_repo') + repo_path = tmproot.join('builtin.mock') + + shutil.copytree(spack.paths.mock_packages_path, str(repo_path)) + mock_repo = spack.repo.RepoPath(str(repo_path)) + mock_repo_packages = mock_repo.repos[0].packages_path + + git = which('git', required=True) + with working_dir(mock_repo_packages): + git('init') + + # initial commit with mock packages + git('add', '.') + git('commit', '-m', 'initial mock repo commit') + + # add commit with pkg-a, pkg-b, pkg-c packages + mkdirp('pkg-a', 'pkg-b', 'pkg-c') + with open('pkg-a/package.py', 'w') as f: + f.write(pkg_template.format(name='PkgA')) + with open('pkg-c/package.py', 'w') as f: + f.write(pkg_template.format(name='PkgB')) + with open('pkg-b/package.py', 'w') as f: + f.write(pkg_template.format(name='PkgC')) + git('add', 'pkg-a', 'pkg-b', 'pkg-c') + git('commit', '-m', 'add pkg-a, pkg-b, pkg-c') + + # remove pkg-c, add pkg-d + with open('pkg-b/package.py', 'a') as f: + f.write('\n# change pkg-b') + git('add', 'pkg-b') + mkdirp('pkg-d') + with open('pkg-d/package.py', 'w') as f: + f.write(pkg_template.format(name='PkgD')) + git('add', 'pkg-d') + git('rm', '-rf', 'pkg-c') + git('commit', '-m', 'change pkg-b, remove pkg-c, add pkg-d') + + with spack.repo.swap(mock_repo): + yield mock_repo_packages + + +@pytest.fixture(scope='module') +def mock_pkg_names(): + repo = spack.repo.path.get_repo('builtin.mock') + names = set(name for name in repo.all_package_names() + if not name.startswith('pkg-')) + return names + + +def split(output): + """Split command line output into an array.""" + output = output.strip() + return re.split(r'\s+', output) if output else [] + + +pkg = spack.main.SpackCommand('pkg') + + +def test_packages_path(): + assert (spack.cmd.pkg.packages_path() == + spack.repo.path.get_repo('builtin').packages_path) + + +def test_mock_packages_path(mock_packages): + assert (spack.cmd.pkg.packages_path() == + spack.repo.path.get_repo('builtin.mock').packages_path) + + +def test_pkg_add(mock_pkg_git_repo): + with working_dir(mock_pkg_git_repo): + mkdirp('pkg-e') + with open('pkg-e/package.py', 'w') as f: + f.write(pkg_template.format(name='PkgE')) + + pkg('add', 'pkg-e') + + git = which('git', required=True) + with working_dir(mock_pkg_git_repo): + try: + assert ('A pkg-e/package.py' in + git('status', '--short', output=str)) + finally: + shutil.rmtree('pkg-e') + + with pytest.raises(spack.main.SpackCommandError): + pkg('add', 'does-not-exist') + + +def test_pkg_list(mock_pkg_git_repo, mock_pkg_names): + out = split(pkg('list', 'HEAD^^')) + assert sorted(mock_pkg_names) == sorted(out) + + out = split(pkg('list', 'HEAD^')) + assert sorted( + mock_pkg_names.union(['pkg-a', 'pkg-b', 'pkg-c'])) == sorted(out) + + out = split(pkg('list', 'HEAD')) + assert sorted( + mock_pkg_names.union(['pkg-a', 'pkg-b', 'pkg-d'])) == sorted(out) + + # test with three dots to make sure pkg calls `git merge-base` + out = split(pkg('list', 'HEAD^^...')) + assert sorted(mock_pkg_names) == sorted(out) + + +def test_pkg_diff(mock_pkg_git_repo, mock_pkg_names): + out = split(pkg('diff', 'HEAD^^', 'HEAD^')) + assert out == ['HEAD^:', 'pkg-a', 'pkg-b', 'pkg-c'] + + out = split(pkg('diff', 'HEAD^^', 'HEAD')) + assert out == ['HEAD:', 'pkg-a', 'pkg-b', 'pkg-d'] + + out = split(pkg('diff', 'HEAD^', 'HEAD')) + assert out == ['HEAD^:', 'pkg-c', 'HEAD:', 'pkg-d'] + + +def test_pkg_added(mock_pkg_git_repo): + out = split(pkg('added', 'HEAD^^', 'HEAD^')) + assert out == ['pkg-a', 'pkg-b', 'pkg-c'] + + out = split(pkg('added', 'HEAD^^', 'HEAD')) + assert out == ['pkg-a', 'pkg-b', 'pkg-d'] + + out = split(pkg('added', 'HEAD^', 'HEAD')) + assert out == ['pkg-d'] + + out = split(pkg('added', 'HEAD', 'HEAD')) + assert out == [] + + +def test_pkg_removed(mock_pkg_git_repo): + out = split(pkg('removed', 'HEAD^^', 'HEAD^')) + assert out == [] + + out = split(pkg('removed', 'HEAD^^', 'HEAD')) + assert out == [] + + out = split(pkg('removed', 'HEAD^', 'HEAD')) + assert out == ['pkg-c'] + + +def test_pkg_changed(mock_pkg_git_repo): + out = split(pkg('changed', 'HEAD^^', 'HEAD^')) + assert out == [] + + out = split(pkg('changed', '--type', 'c', 'HEAD^^', 'HEAD^')) + assert out == [] + + out = split(pkg('changed', '--type', 'a', 'HEAD^^', 'HEAD^')) + assert out == ['pkg-a', 'pkg-b', 'pkg-c'] + + out = split(pkg('changed', '--type', 'r', 'HEAD^^', 'HEAD^')) + assert out == [] + + out = split(pkg('changed', '--type', 'ar', 'HEAD^^', 'HEAD^')) + assert out == ['pkg-a', 'pkg-b', 'pkg-c'] + + out = split(pkg('changed', '--type', 'arc', 'HEAD^^', 'HEAD^')) + assert out == ['pkg-a', 'pkg-b', 'pkg-c'] + + out = split(pkg('changed', 'HEAD^', 'HEAD')) + assert out == ['pkg-b'] + + out = split(pkg('changed', '--type', 'c', 'HEAD^', 'HEAD')) + assert out == ['pkg-b'] + + out = split(pkg('changed', '--type', 'a', 'HEAD^', 'HEAD')) + assert out == ['pkg-d'] + + out = split(pkg('changed', '--type', 'r', 'HEAD^', 'HEAD')) + assert out == ['pkg-c'] + + out = split(pkg('changed', '--type', 'ar', 'HEAD^', 'HEAD')) + assert out == ['pkg-c', 'pkg-d'] + + out = split(pkg('changed', '--type', 'arc', 'HEAD^', 'HEAD')) + assert out == ['pkg-b', 'pkg-c', 'pkg-d'] + + # invalid type argument + with pytest.raises(spack.main.SpackCommandError): + pkg('changed', '--type', 'foo') + + +def test_pkg_fails_when_not_git_repo(monkeypatch): + monkeypatch.setattr(spack.cmd, 'spack_is_git_repo', lambda: False) + with pytest.raises(spack.main.SpackCommandError): + pkg('added') -- cgit v1.2.3-70-g09d2