diff --git a/lib/spack/spack/cmd/common/arguments.py b/lib/spack/spack/cmd/common/arguments.py
index 11c90b2ee40dc8fd02916e40c5175a7a0d6960b7..46be346837873bec9269d4c77331f622c47e3c9c 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 @@ def __call__(self, parser, namespace, values, option_string=None):
 _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 02ff10f425468dee71e310ae8cd666b6410854dc..b7aa390c592dc45c187b917122294cc71117d8a6 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 b7f824c09154952bb58ff15c4e41bf5d6a61bc43..323699871064e763c82eef01391a76e4b9093c88 100644
--- a/lib/spack/spack/cmd/info.py
+++ b/lib/spack/spack/cmd/info.py
@@ -26,15 +26,15 @@
 
 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)
 
@@ -167,6 +167,14 @@ def print_text_info(pkg):
         color.cprint('')
         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:  '))
 
@@ -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 5b915b94ecb70980ab4de05c71d7caa564979c35..730ad8d9d923ccca1011d97687fe6495e189fbb0 100644
--- a/lib/spack/spack/cmd/list.py
+++ b/lib/spack/spack/cmd/list.py
@@ -29,12 +29,15 @@
 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 f09be4ecd53a93a264320ab1f1ec9fb630680ee0..6a6d59942e3a0b1f0d7d1d58c4f530e05f69202b 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 0a6a774315ae1b702cdb404da0e4b53e9ab7d98d..fbca9474fbe7c2d74c19088d0776a80e3e2b7182 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 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 @@ def __getattr__(self, name):
         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 @@ def all_package_names(self):
             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 @@ def check(condition, msg):
         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 @@ def purge(self):
         """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 @@ def filename_for_package_name(self, spec):
         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 @@ def all_packages(self):
 
     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 b082b71c06143b5cca1f0eba5b966ac3486ead5f..57dd6883627d526efa6131b0ad63f0a9b6dd94fe 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 9819f2cd84f1578f424bd6013c0323135f8b75a0..6c71515a3f0283ffda5ff7f901b1d8dbd28c2627 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 @@
 ])
 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 0000000000000000000000000000000000000000..244ae5fcbceb7ffa94cce4dbb2e8d17c878d201b
--- /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 28d7b57f2df910b95ffd8e54ea9c8249462a2383..844cfb940c94128b4ff4a281cbc93805905f8aec 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 bdb5f914c9afbeedb28bfaa3e0fef83b77c68d69..aabd5232d7cc930649590f9320e0e9be594d93af 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 863c8a298068bb4a8f0ad47d96894526efff91a7..3b0e0e4c26358e1dd0a9a64665adfd52d5b35b4e 100644
--- a/var/spack/repos/builtin/packages/aspa/package.py
+++ b/var/spack/repos/builtin/packages/aspa/package.py
@@ -28,10 +28,9 @@
 
 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"