summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMassimiliano Culpo <massimiliano.culpo@googlemail.com>2017-09-06 03:44:42 +0200
committerTodd Gamblin <tgamblin@llnl.gov>2017-09-05 15:44:42 -1000
commitd1a5857a03dc2e21f5514fa2bcfafe0ec0210f12 (patch)
tree9a43088f481c70279da60d807326eac7f6dbc9b8
parentfeefdedadf224315d69ef2f6da1fafd6338bba5a (diff)
downloadspack-d1a5857a03dc2e21f5514fa2bcfafe0ec0210f12.tar.gz
spack-d1a5857a03dc2e21f5514fa2bcfafe0ec0210f12.tar.bz2
spack-d1a5857a03dc2e21f5514fa2bcfafe0ec0210f12.tar.xz
spack-d1a5857a03dc2e21f5514fa2bcfafe0ec0210f12.zip
Added support for querying by tags (#4786)
* Added support to query packages by tags. - The querying commands `spack list`, `spack find` and `spack info` have been modified to support querying by tags. Tests have been added to check that the feature is working correctly under what should be the most frequent use cases. * Refactored Repo class to make insertion of new file caches easier. - Added the class FastPackageChecker. This class is a Mapping from package names to stat info, that gets memoized for faster access. - Extracted the creation of a ProviderIndex to its own factory function. * Added a cache file for tags. - Following what was done for providers, a TagIndex class has been added. This class can serialize and deserialize objects from json. Repo and RepoPath have a new method 'packages_with_tags', that uses the TagIndex to compute a list of package names that have all the tags passed as arguments. On Ubuntu 14.04 the effect if the cache reduces the time for spack list from ~3sec. to ~0.3sec. after the cache has been built. * Fixed colorization of `spack info`
-rw-r--r--lib/spack/spack/cmd/common/arguments.py14
-rw-r--r--lib/spack/spack/cmd/find.py13
-rw-r--r--lib/spack/spack/cmd/info.py26
-rw-r--r--lib/spack/spack/cmd/list.py14
-rw-r--r--lib/spack/spack/file_cache.py2
-rw-r--r--lib/spack/spack/repository.py394
-rw-r--r--lib/spack/spack/test/cmd/find.py57
-rw-r--r--lib/spack/spack/test/cmd/info.py52
-rw-r--r--lib/spack/spack/test/cmd/list.py73
-rw-r--r--var/spack/repos/builtin.mock/packages/mpich/package.py2
-rw-r--r--var/spack/repos/builtin.mock/packages/mpich2/package.py2
-rw-r--r--var/spack/repos/builtin/packages/aspa/package.py7
12 files changed, 525 insertions, 131 deletions
diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py
index 11c90b2ee4..46be346837 100644
--- a/lib/spack/spack/cmd/common/arguments.py
+++ b/lib/spack/spack/cmd/common/arguments.py
@@ -26,15 +26,23 @@
import argparse
import spack.cmd
-import spack.store
import spack.modules
+import spack.spec
+import spack.store
from spack.util.pattern import Args
+
__all__ = ['add_common_arguments']
_arguments = {}
def add_common_arguments(parser, list_of_arguments):
+ """Extend a parser with extra arguments
+
+ Args:
+ parser: parser to be extended
+ list_of_arguments: arguments to be added to the parser
+ """
for argument in list_of_arguments:
if argument not in _arguments:
message = 'Trying to add non existing argument "{0}" to a command'
@@ -133,3 +141,7 @@ _arguments['long'] = Args(
_arguments['very_long'] = Args(
'-L', '--very-long', action='store_true',
help='show full dependency hashes as well as versions')
+
+_arguments['tags'] = Args(
+ '-t', '--tags', action='append',
+ help='filter a package query by tags')
diff --git a/lib/spack/spack/cmd/find.py b/lib/spack/spack/cmd/find.py
index 02ff10f425..b7aa390c59 100644
--- a/lib/spack/spack/cmd/find.py
+++ b/lib/spack/spack/cmd/find.py
@@ -25,8 +25,8 @@
import sys
import llnl.util.tty as tty
+import spack
import spack.cmd.common.arguments as arguments
-
from spack.cmd import display_specs
description = "list and search installed packages"
@@ -54,7 +54,7 @@ def setup_parser(subparser):
const='deps',
help='show full dependency DAG of installed packages')
- arguments.add_common_arguments(subparser, ['long', 'very_long'])
+ arguments.add_common_arguments(subparser, ['long', 'very_long', 'tags'])
subparser.add_argument('-f', '--show-flags',
action='store_true',
@@ -123,11 +123,16 @@ def find(parser, args):
# Exit early if no package matches the constraint
if not query_specs and args.constraint:
- msg = "No package matches the query: {0}".format(
- ' '.join(args.constraint))
+ msg = "No package matches the query: {0}"
+ msg = msg.format(' '.join(args.constraint))
tty.msg(msg)
return
+ # If tags have been specified on the command line, filter by tags
+ if args.tags:
+ packages_with_tags = spack.repo.packages_with_tags(*args.tags)
+ query_specs = [x for x in query_specs if x.name in packages_with_tags]
+
# Display the result
if sys.stdout.isatty():
tty.msg("%d installed packages." % len(query_specs))
diff --git a/lib/spack/spack/cmd/info.py b/lib/spack/spack/cmd/info.py
index b7f824c091..3236998710 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -26,15 +26,15 @@ from __future__ import print_function
import textwrap
+from six.moves import zip_longest
+
+from llnl.util.tty.colify import *
+
import llnl.util.tty.color as color
import spack
import spack.fetch_strategy as fs
import spack.spec
-from llnl.util.tty.colify import *
-
-from six.moves import zip_longest
-
description = 'get detailed information on a particular package'
section = 'basic'
level = 'short'
@@ -156,9 +156,9 @@ def print_text_info(pkg):
color.cprint('')
color.cprint(section_title('Description:'))
if pkg.__doc__:
- print(pkg.format_doc(indent=4))
+ color.cprint(pkg.format_doc(indent=4))
else:
- print(" None")
+ color.cprint(" None")
color.cprint(section_title('Homepage: ') + pkg.homepage)
@@ -168,6 +168,14 @@ def print_text_info(pkg):
color.cprint(section_title('Maintainers: ') + mnt)
color.cprint('')
+ color.cprint(section_title("Tags: "))
+ if hasattr(pkg, 'tags'):
+ tags = sorted(pkg.tags)
+ colify(tags, indent=4)
+ else:
+ color.cprint(" None")
+
+ color.cprint('')
color.cprint(section_title('Preferred version: '))
if not pkg.versions:
@@ -220,7 +228,7 @@ def print_text_info(pkg):
if deps:
colify(deps, indent=4)
else:
- print(' None')
+ color.cprint(' None')
color.cprint('')
color.cprint(section_title('Virtual Packages: '))
@@ -238,7 +246,9 @@ def print_text_info(pkg):
print(line)
else:
- print(" None")
+ color.cprint(" None")
+
+ color.cprint('')
def info(parser, args):
diff --git a/lib/spack/spack/cmd/list.py b/lib/spack/spack/cmd/list.py
index 5b915b94ec..730ad8d9d9 100644
--- a/lib/spack/spack/cmd/list.py
+++ b/lib/spack/spack/cmd/list.py
@@ -29,12 +29,15 @@ import cgi
import fnmatch
import re
import sys
+
from six import StringIO
import llnl.util.tty as tty
-import spack
from llnl.util.tty.colify import colify
+import spack
+import spack.cmd.common.arguments as arguments
+
description = "list and search available packages"
section = "basic"
level = "short"
@@ -60,6 +63,8 @@ def setup_parser(subparser):
'--format', default='name_only', choices=formatters,
help='format to be used to print the output [default: name_only]')
+ arguments.add_common_arguments(subparser, ['tags'])
+
def filter_by_name(pkgs, args):
"""
@@ -183,5 +188,12 @@ def list(parser, args):
pkgs = set(spack.repo.all_package_names())
# Filter the set appropriately
sorted_packages = filter_by_name(pkgs, args)
+
+ # Filter by tags
+ if args.tags:
+ packages_with_tags = set(spack.repo.packages_with_tags(*args.tags))
+ sorted_packages = set(sorted_packages) & packages_with_tags
+ sorted_packages = sorted(sorted_packages)
+
# Print to stdout
formatters[args.format](sorted_packages)
diff --git a/lib/spack/spack/file_cache.py b/lib/spack/spack/file_cache.py
index f09be4ecd5..6a6d59942e 100644
--- a/lib/spack/spack/file_cache.py
+++ b/lib/spack/spack/file_cache.py
@@ -35,7 +35,7 @@ class FileCache(object):
"""This class manages cached data in the filesystem.
- Cache files are fetched and stored by unique keys. Keys can be relative
- paths, so that thre can be some hierarchy in the cache.
+ paths, so that there can be some hierarchy in the cache.
- The FileCache handles locking cache files for reading and writing, so
client code need not manage locks for cache entries.
diff --git a/lib/spack/spack/repository.py b/lib/spack/spack/repository.py
index 0a6a774315..fbca9474fb 100644
--- a/lib/spack/spack/repository.py
+++ b/lib/spack/spack/repository.py
@@ -22,6 +22,7 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import collections
import os
import stat
import shutil
@@ -31,11 +32,18 @@ import inspect
import imp
import re
import traceback
-from bisect import bisect_left
+import json
+
+try:
+ from collections.abc import Mapping
+except ImportError:
+ from collections import Mapping
+
from types import ModuleType
import yaml
+import llnl.util.lang
import llnl.util.tty as tty
from llnl.util.filesystem import *
@@ -93,6 +101,242 @@ class SpackNamespace(ModuleType):
return getattr(self, name)
+class FastPackageChecker(Mapping):
+ """Cache that maps package names to the stats obtained on the
+ 'package.py' files associated with them.
+
+ For each repository a cache is maintained at class level, and shared among
+ all instances referring to it. Update of the global cache is done lazily
+ during instance initialization.
+ """
+ #: Global cache, reused by every instance
+ _paths_cache = {}
+
+ def __init__(self, packages_path):
+
+ #: The path of the repository managed by this instance
+ self.packages_path = packages_path
+
+ # If the cache we need is not there yet, then build it appropriately
+ if packages_path not in self._paths_cache:
+ self._paths_cache[packages_path] = self._create_new_cache()
+
+ #: Reference to the appropriate entry in the global cache
+ self._packages_to_stats = self._paths_cache[packages_path]
+
+ def _create_new_cache(self):
+ """Create a new cache for packages in a repo.
+
+ The implementation here should try to minimize filesystem
+ calls. At the moment, it is O(number of packages) and makes
+ about one stat call per package. This is reasonably fast, and
+ avoids actually importing packages in Spack, which is slow.
+ """
+ # Create a dictionary that will store the mapping between a
+ # package name and its stat info
+ cache = {}
+ for pkg_name in os.listdir(self.packages_path):
+ # Skip non-directories in the package root.
+ pkg_dir = join_path(self.packages_path, pkg_name)
+
+ # Warn about invalid names that look like packages.
+ if not valid_module_name(pkg_name):
+ msg = 'Skipping package at {0}. '
+ msg += '"{1]" is not a valid Spack module name.'
+ tty.warn(msg.format(pkg_dir, pkg_name))
+ continue
+
+ # Construct the file name from the directory
+ pkg_file = os.path.join(
+ self.packages_path, pkg_name, package_file_name
+ )
+
+ # Use stat here to avoid lots of calls to the filesystem.
+ try:
+ sinfo = os.stat(pkg_file)
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ # No package.py file here.
+ continue
+ elif e.errno == errno.EACCES:
+ tty.warn("Can't read package file %s." % pkg_file)
+ continue
+ raise e
+
+ # If it's not a file, skip it.
+ if stat.S_ISDIR(sinfo.st_mode):
+ continue
+
+ # If it is a file, then save the stats under the
+ # appropriate key
+ cache[pkg_name] = sinfo
+
+ return cache
+
+ def __getitem__(self, item):
+ return self._packages_to_stats[item]
+
+ def __iter__(self):
+ return iter(self._packages_to_stats)
+
+ def __len__(self):
+ return len(self._packages_to_stats)
+
+
+class TagIndex(Mapping):
+ """Maps tags to list of packages."""
+
+ def __init__(self):
+ self._tag_dict = collections.defaultdict(list)
+
+ def to_json(self, stream):
+ json.dump({'tags': self._tag_dict}, stream)
+
+ @staticmethod
+ def from_json(stream):
+ d = json.load(stream)
+
+ r = TagIndex()
+
+ for tag, list in d['tags'].items():
+ r[tag].extend(list)
+
+ return r
+
+ def __getitem__(self, item):
+ return self._tag_dict[item]
+
+ def __iter__(self):
+ return iter(self._tag_dict)
+
+ def __len__(self):
+ return len(self._tag_dict)
+
+ def update_package(self, pkg_name):
+ """Updates a package in the tag index.
+
+ Args:
+ pkg_name (str): name of the package to be removed from the index
+
+ """
+
+ package = spack.repo.get(pkg_name)
+
+ # Remove the package from the list of packages, if present
+ for pkg_list in self._tag_dict.values():
+ if pkg_name in pkg_list:
+ pkg_list.remove(pkg_name)
+
+ # Add it again under the appropriate tags
+ for tag in getattr(package, 'tags', []):
+ self._tag_dict[tag].append(package.name)
+
+
+@llnl.util.lang.memoized
+def make_provider_index_cache(packages_path, namespace):
+ """Lazily updates the provider index cache associated with a repository,
+ if need be, then returns it. Caches results for later look-ups.
+
+ Args:
+ packages_path: path of the repository
+ namespace: namespace of the repository
+
+ Returns:
+ instance of ProviderIndex
+ """
+ # Map that goes from package names to stat info
+ fast_package_checker = FastPackageChecker(packages_path)
+
+ # Filename of the provider index cache
+ cache_filename = 'providers/{0}-index.yaml'.format(namespace)
+
+ # Compute which packages needs to be updated in the cache
+ index_mtime = spack.misc_cache.mtime(cache_filename)
+
+ needs_update = [
+ x for x, sinfo in fast_package_checker.items()
+ if sinfo.st_mtime > index_mtime
+ ]
+
+ # Read the old ProviderIndex, or make a new one.
+ index_existed = spack.misc_cache.init_entry(cache_filename)
+
+ if index_existed and not needs_update:
+
+ # If the provider index exists and doesn't need an update
+ # just read from it
+ with spack.misc_cache.read_transaction(cache_filename) as f:
+ index = ProviderIndex.from_yaml(f)
+
+ else:
+
+ # Otherwise we need a write transaction to update it
+ with spack.misc_cache.write_transaction(cache_filename) as (old, new):
+
+ index = ProviderIndex.from_yaml(old) if old else ProviderIndex()
+
+ for pkg_name in needs_update:
+ namespaced_name = '{0}.{1}'.format(namespace, pkg_name)
+ index.remove_provider(namespaced_name)
+ index.update(namespaced_name)
+
+ index.to_yaml(new)
+
+ return index
+
+
+@llnl.util.lang.memoized
+def make_tag_index_cache(packages_path, namespace):
+ """Lazily updates the tag index cache associated with a repository,
+ if need be, then returns it. Caches results for later look-ups.
+
+ Args:
+ packages_path: path of the repository
+ namespace: namespace of the repository
+
+ Returns:
+ instance of TagIndex
+ """
+ # Map that goes from package names to stat info
+ fast_package_checker = FastPackageChecker(packages_path)
+
+ # Filename of the provider index cache
+ cache_filename = 'tags/{0}-index.json'.format(namespace)
+
+ # Compute which packages needs to be updated in the cache
+ index_mtime = spack.misc_cache.mtime(cache_filename)
+
+ needs_update = [
+ x for x, sinfo in fast_package_checker.items()
+ if sinfo.st_mtime > index_mtime
+ ]
+
+ # Read the old ProviderIndex, or make a new one.
+ index_existed = spack.misc_cache.init_entry(cache_filename)
+
+ if index_existed and not needs_update:
+
+ # If the provider index exists and doesn't need an update
+ # just read from it
+ with spack.misc_cache.read_transaction(cache_filename) as f:
+ index = TagIndex.from_json(f)
+
+ else:
+
+ # Otherwise we need a write transaction to update it
+ with spack.misc_cache.write_transaction(cache_filename) as (old, new):
+
+ index = TagIndex.from_json(old) if old else TagIndex()
+
+ for pkg_name in needs_update:
+ namespaced_name = '{0}.{1}'.format(namespace, pkg_name)
+ index.update_package(namespaced_name)
+
+ index.to_json(new)
+
+ return index
+
+
class RepoPath(object):
"""A RepoPath is a list of repos that function as one.
@@ -220,6 +464,12 @@ class RepoPath(object):
self._all_package_names = sorted(all_pkgs, key=lambda n: n.lower())
return self._all_package_names
+ def packages_with_tags(self, *tags):
+ r = set()
+ for repo in self.repos:
+ r |= set(repo.packages_with_tags(*tags))
+ return sorted(r)
+
def all_packages(self):
for name in self.all_package_names():
yield self.get(name)
@@ -422,21 +672,18 @@ class Repo(object):
self._classes = {}
self._instances = {}
- # list of packages that are newer than the index.
- self._needs_update = []
+ # Maps that goes from package name to corresponding file stat
+ self._fast_package_checker = FastPackageChecker(self.packages_path)
- # Index of virtual dependencies
+ # Index of virtual dependencies, computed lazily
self._provider_index = None
- # Cached list of package names.
- self._all_package_names = None
+ # Index of tags, computed lazily
+ self._tag_index = None
# make sure the namespace for packages in this repo exists.
self._create_namespace()
- # Unique filename for cache of virtual dependency providers
- self._cache_file = 'providers/%s-index.yaml' % self.namespace
-
def _create_namespace(self):
"""Create this repo's namespace module and insert it into sys.modules.
@@ -617,41 +864,28 @@ class Repo(object):
"""Clear entire package instance cache."""
self._instances.clear()
- def _update_provider_index(self):
- # Check modification dates of all packages
- self._fast_package_check()
-
- def read():
- with open(self.index_file) as f:
- self._provider_index = ProviderIndex.from_yaml(f)
-
- # Read the old ProviderIndex, or make a new one.
- key = self._cache_file
- index_existed = spack.misc_cache.init_entry(key)
- if index_existed and not self._needs_update:
- with spack.misc_cache.read_transaction(key) as f:
- self._provider_index = ProviderIndex.from_yaml(f)
- else:
- with spack.misc_cache.write_transaction(key) as (old, new):
- if old:
- self._provider_index = ProviderIndex.from_yaml(old)
- else:
- self._provider_index = ProviderIndex()
-
- for pkg_name in self._needs_update:
- namespaced_name = '%s.%s' % (self.namespace, pkg_name)
- self._provider_index.remove_provider(namespaced_name)
- self._provider_index.update(namespaced_name)
-
- self._provider_index.to_yaml(new)
-
@property
def provider_index(self):
"""A provider index with names *specific* to this repo."""
+
if self._provider_index is None:
- self._update_provider_index()
+ self._provider_index = make_provider_index_cache(
+ self.packages_path, self.namespace
+ )
+
return self._provider_index
+ @property
+ def tag_index(self):
+ """A provider index with names *specific* to this repo."""
+
+ if self._tag_index is None:
+ self._tag_index = make_tag_index_cache(
+ self.packages_path, self.namespace
+ )
+
+ return self._tag_index
+
@_autospec
def providers_for(self, vpkg_spec):
providers = self.provider_index.providers_for(vpkg_spec)
@@ -689,73 +923,18 @@ class Repo(object):
pkg_dir = self.dirname_for_package_name(spec.name)
return join_path(pkg_dir, package_file_name)
- def _fast_package_check(self):
- """List packages in the repo and check whether index is up to date.
-
- Both of these opreations require checking all `package.py`
- files so we do them at the same time. We list the repo
- directory and look at package.py files, and we compare the
- index modification date with the ost recently modified package
- file, storing the result.
-
- The implementation here should try to minimize filesystem
- calls. At the moment, it is O(number of packages) and makes
- about one stat call per package. This is resonably fast, and
- avoids actually importing packages in Spack, which is slow.
-
- """
- if self._all_package_names is None:
- self._all_package_names = []
-
- # Get index modification time.
- index_mtime = spack.misc_cache.mtime(self._cache_file)
-
- for pkg_name in os.listdir(self.packages_path):
- # Skip non-directories in the package root.
- pkg_dir = join_path(self.packages_path, pkg_name)
-
- # Warn about invalid names that look like packages.
- if not valid_module_name(pkg_name):
- msg = ("Skipping package at %s. "
- "'%s' is not a valid Spack module name.")
- tty.warn(msg % (pkg_dir, pkg_name))
- continue
-
- # construct the file name from the directory
- pkg_file = join_path(
- self.packages_path, pkg_name, package_file_name)
-
- # Use stat here to avoid lots of calls to the filesystem.
- try:
- sinfo = os.stat(pkg_file)
- except OSError as e:
- if e.errno == errno.ENOENT:
- # No package.py file here.
- continue
- elif e.errno == errno.EACCES:
- tty.warn("Can't read package file %s." % pkg_file)
- continue
- raise e
-
- # if it's not a file, skip it.
- if stat.S_ISDIR(sinfo.st_mode):
- continue
-
- # All checks passed. Add it to the list.
- self._all_package_names.append(pkg_name)
-
- # record the package if it is newer than the index.
- if sinfo.st_mtime > index_mtime:
- self._needs_update.append(pkg_name)
+ def all_package_names(self):
+ """Returns a sorted list of all package names in the Repo."""
+ return sorted(self._fast_package_checker.keys())
- self._all_package_names.sort()
+ def packages_with_tags(self, *tags):
+ v = set(self.all_package_names())
+ index = self.tag_index
- return self._all_package_names
+ for t in tags:
+ v &= set(index[t])
- def all_package_names(self):
- """Returns a sorted list of all package names in the Repo."""
- self._fast_package_check()
- return self._all_package_names
+ return sorted(v)
def all_packages(self):
"""Iterator over all packages in the repository.
@@ -768,16 +947,7 @@ class Repo(object):
def exists(self, pkg_name):
"""Whether a package with the supplied name exists."""
- if self._all_package_names:
- # This does a binary search in the sorted list.
- idx = bisect_left(self.all_package_names(), pkg_name)
- return (idx < len(self._all_package_names) and
- self._all_package_names[idx] == pkg_name)
-
- # If we haven't generated the full package list, don't.
- # Just check whether the file exists.
- filename = self.filename_for_package_name(pkg_name)
- return os.path.exists(filename)
+ return pkg_name in self._fast_package_checker
def is_virtual(self, pkg_name):
"""True if the package with this name is virtual, False otherwise."""
diff --git a/lib/spack/spack/test/cmd/find.py b/lib/spack/spack/test/cmd/find.py
index b082b71c06..57dd688362 100644
--- a/lib/spack/spack/test/cmd/find.py
+++ b/lib/spack/spack/test/cmd/find.py
@@ -22,12 +22,40 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import argparse
+
+import pytest
import spack.cmd.find
from spack.util.pattern import Bunch
+@pytest.fixture(scope='module')
+def parser():
+ """Returns the parser for the module command"""
+ prs = argparse.ArgumentParser()
+ spack.cmd.find.setup_parser(prs)
+ return prs
+
+
+@pytest.fixture()
+def specs():
+ s = []
+ return s
+
+
+@pytest.fixture()
+def mock_display(monkeypatch, specs):
+ """Monkeypatches the display function to return its first argument"""
+
+ def display(x, *args, **kwargs):
+ specs.extend(x)
+
+ monkeypatch.setattr(spack.cmd.find, 'display_specs', display)
+
+
def test_query_arguments():
query_arguments = spack.cmd.find.query_arguments
+
# Default arguments
args = Bunch(
only_missing=False,
@@ -36,6 +64,7 @@ def test_query_arguments():
explicit=False,
implicit=False
)
+
q_args = query_arguments(args)
assert 'installed' in q_args
assert 'known' in q_args
@@ -43,11 +72,39 @@ def test_query_arguments():
assert q_args['installed'] is True
assert q_args['known'] is any
assert q_args['explicit'] is any
+
# Check that explicit works correctly
args.explicit = True
q_args = query_arguments(args)
assert q_args['explicit'] is True
+
args.explicit = False
args.implicit = True
q_args = query_arguments(args)
assert q_args['explicit'] is False
+
+
+@pytest.mark.usefixtures('database', 'mock_display')
+class TestFindWithTags(object):
+
+ def test_tag1(self, parser, specs):
+
+ args = parser.parse_args(['--tags', 'tag1'])
+ spack.cmd.find.find(parser, args)
+
+ assert len(specs) == 2
+ assert 'mpich' in [x.name for x in specs]
+ assert 'mpich2' in [x.name for x in specs]
+
+ def test_tag2(self, parser, specs):
+ args = parser.parse_args(['--tags', 'tag2'])
+ spack.cmd.find.find(parser, args)
+
+ assert len(specs) == 1
+ assert 'mpich' in [x.name for x in specs]
+
+ def test_tag2_tag3(self, parser, specs):
+ args = parser.parse_args(['--tags', 'tag2', '--tags', 'tag3'])
+ spack.cmd.find.find(parser, args)
+
+ assert len(specs) == 0
diff --git a/lib/spack/spack/test/cmd/info.py b/lib/spack/spack/test/cmd/info.py
index 9819f2cd84..6c71515a3f 100644
--- a/lib/spack/spack/test/cmd/info.py
+++ b/lib/spack/spack/test/cmd/info.py
@@ -22,13 +22,39 @@
# License along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
##############################################################################
+import argparse
+
import pytest
+import spack.cmd.info
from spack.main import SpackCommand
info = SpackCommand('info')
+@pytest.fixture(scope='module')
+def parser():
+ """Returns the parser for the module command"""
+ prs = argparse.ArgumentParser()
+ spack.cmd.info.setup_parser(prs)
+ return prs
+
+
+@pytest.fixture()
+def info_lines():
+ lines = []
+ return lines
+
+
+@pytest.fixture()
+def mock_print(monkeypatch, info_lines):
+
+ def _print(*args):
+ info_lines.extend(args)
+
+ monkeypatch.setattr(spack.cmd.info.color, 'cprint', _print, raising=False)
+
+
@pytest.mark.parametrize('pkg', [
'openmpi',
'trilinos',
@@ -38,3 +64,29 @@ info = SpackCommand('info')
])
def test_it_just_runs(pkg):
info(pkg)
+
+
+@pytest.mark.parametrize('pkg_query', [
+ 'hdf5',
+ 'cloverleaf3d',
+ 'trilinos'
+])
+@pytest.mark.usefixtures('mock_print')
+def test_info_fields(pkg_query, parser, info_lines):
+
+ expected_fields = (
+ 'Description:',
+ 'Homepage:',
+ 'Safe versions:',
+ 'Variants:',
+ 'Installation Phases:',
+ 'Virtual Packages:',
+ 'Tags:'
+ )
+
+ args = parser.parse_args([pkg_query])
+ spack.cmd.info.info(parser, args)
+
+ for text in expected_fields:
+ match = [x for x in info_lines if text in x]
+ assert match
diff --git a/lib/spack/spack/test/cmd/list.py b/lib/spack/spack/test/cmd/list.py
new file mode 100644
index 0000000000..244ae5fcbc
--- /dev/null
+++ b/lib/spack/spack/test/cmd/list.py
@@ -0,0 +1,73 @@
+##############################################################################
+# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC.
+# Produced at the Lawrence Livermore National Laboratory.
+#
+# This file is part of Spack.
+# Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved.
+# LLNL-CODE-647188
+#
+# For details, see https://github.com/llnl/spack
+# Please also see the NOTICE and LICENSE files for our notice and the LGPL.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (as
+# published by the Free Software Foundation) version 2.1, February 1999.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the IMPLIED WARRANTY OF
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the terms and
+# conditions of the GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##############################################################################
+import argparse
+
+import pytest
+import spack.cmd.list
+
+
+@pytest.fixture(scope='module')
+def parser():
+ """Returns the parser for the module command"""
+ prs = argparse.ArgumentParser()
+ spack.cmd.list.setup_parser(prs)
+ return prs
+
+
+@pytest.fixture()
+def pkg_names():
+ pkg_names = []
+ return pkg_names
+
+
+@pytest.fixture()
+def mock_name_only(monkeypatch, pkg_names):
+
+ def name_only(x):
+ pkg_names.extend(x)
+
+ monkeypatch.setattr(spack.cmd.list, 'name_only', name_only)
+ monkeypatch.setitem(spack.cmd.list.formatters, 'name_only', name_only)
+
+
+@pytest.mark.usefixtures('mock_name_only')
+class TestListCommand(object):
+
+ def test_list_without_filters(self, parser, pkg_names):
+
+ args = parser.parse_args([])
+ spack.cmd.list.list(parser, args)
+
+ assert pkg_names
+ assert 'cloverleaf3d' in pkg_names
+ assert 'hdf5' in pkg_names
+
+ def test_list_with_filters(self, parser, pkg_names):
+ args = parser.parse_args(['--tags', 'proxy-app'])
+ spack.cmd.list.list(parser, args)
+
+ assert pkg_names
+ assert 'cloverleaf3d' in pkg_names
+ assert 'hdf5' not in pkg_names \ No newline at end of file
diff --git a/var/spack/repos/builtin.mock/packages/mpich/package.py b/var/spack/repos/builtin.mock/packages/mpich/package.py
index 28d7b57f2d..844cfb940c 100644
--- a/var/spack/repos/builtin.mock/packages/mpich/package.py
+++ b/var/spack/repos/builtin.mock/packages/mpich/package.py
@@ -31,6 +31,8 @@ class Mpich(Package):
list_url = "http://www.mpich.org/static/downloads/"
list_depth = 2
+ tags = ['tag1', 'tag2']
+
variant('debug', default=False,
description="Compile MPICH with debug flags.")
diff --git a/var/spack/repos/builtin.mock/packages/mpich2/package.py b/var/spack/repos/builtin.mock/packages/mpich2/package.py
index bdb5f914c9..aabd5232d7 100644
--- a/var/spack/repos/builtin.mock/packages/mpich2/package.py
+++ b/var/spack/repos/builtin.mock/packages/mpich2/package.py
@@ -31,6 +31,8 @@ class Mpich2(Package):
list_url = "http://www.mpich.org/static/downloads/"
list_depth = 2
+ tags = ['tag1', 'tag3']
+
version('1.5', '9c5d5d4fe1e17dd12153f40bc5b6dbc0')
version('1.4', 'foobarbaz')
version('1.3', 'foobarbaz')
diff --git a/var/spack/repos/builtin/packages/aspa/package.py b/var/spack/repos/builtin/packages/aspa/package.py
index 863c8a2980..3b0e0e4c26 100644
--- a/var/spack/repos/builtin/packages/aspa/package.py
+++ b/var/spack/repos/builtin/packages/aspa/package.py
@@ -28,10 +28,9 @@ import glob
class Aspa(MakefilePackage):
"""A fundamental premise in ExMatEx is that scale-bridging performed in
- heterogeneous MPMD materials science simulations will place important
- demands upon the exascale ecosystem that need to be identified and
- quantified.
- tags = proxy-app
+ heterogeneous MPMD materials science simulations will place important
+ demands upon the exascale ecosystem that need to be identified and
+ quantified.
"""
tags = ['proxy-app']
homepage = "http://www.exmatex.org/aspa.html"