summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTodd Gamblin <tgamblin@llnl.gov>2017-07-22 21:27:54 -0700
committerGitHub <noreply@github.com>2017-07-22 21:27:54 -0700
commitf159246d1d073ae7230b5412a4a2a20eac7f49c2 (patch)
tree22bc9233b630669ae351f5480a2d8dd866e3ba9c
parentc07d93a3e5101d72939ab0feb841a98b4bc36cb8 (diff)
downloadspack-f159246d1d073ae7230b5412a4a2a20eac7f49c2.tar.gz
spack-f159246d1d073ae7230b5412a4a2a20eac7f49c2.tar.bz2
spack-f159246d1d073ae7230b5412a4a2a20eac7f49c2.tar.xz
spack-f159246d1d073ae7230b5412a4a2a20eac7f49c2.zip
Make testing spack commands simpler (#4868)
Adds SpackCommand class allowing Spack commands to be easily in Python Example usage: from spack.main import SpackCommand info = SpackCommand('info') out, err = info('mpich') print(info.returncode) This allows easier testing of Spack commands. Also: * Simplify command tests * Simplify mocking in command tests. * Simplify module command test * Simplify python command test * Simplify uninstall command test * Simplify url command test * SpackCommand uses more compatible output redirection
-rw-r--r--lib/spack/spack/cmd/__init__.py8
-rw-r--r--lib/spack/spack/main.py107
-rw-r--r--lib/spack/spack/test/cmd/gpg.py113
-rw-r--r--lib/spack/spack/test/cmd/install.py207
-rw-r--r--lib/spack/spack/test/cmd/module.py5
-rw-r--r--lib/spack/spack/test/cmd/python.py22
-rw-r--r--lib/spack/spack/test/cmd/uninstall.py33
-rw-r--r--lib/spack/spack/test/cmd/url.py72
-rw-r--r--lib/spack/spack/test/conftest.py47
-rw-r--r--lib/spack/spack/test/install.py70
-rw-r--r--var/spack/repos/builtin.mock/packages/a/package.py2
-rw-r--r--var/spack/repos/builtin.mock/packages/libdwarf/package.py2
-rw-r--r--var/spack/repos/builtin.mock/packages/libelf/package.py9
13 files changed, 299 insertions, 398 deletions
diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py
index f691038734..edeaa677de 100644
--- a/lib/spack/spack/cmd/__init__.py
+++ b/lib/spack/spack/cmd/__init__.py
@@ -75,7 +75,8 @@ def remove_options(parser, *options):
break
-def get_cmd_function_name(name):
+def get_python_name(name):
+ """Commands can have '-' in their names, unlike Python identifiers."""
return name.replace("-", "_")
@@ -89,7 +90,7 @@ def get_module(name):
attr_setdefault(module, SETUP_PARSER, lambda *args: None) # null-op
attr_setdefault(module, DESCRIPTION, "")
- fn_name = get_cmd_function_name(name)
+ fn_name = get_python_name(name)
if not hasattr(module, fn_name):
tty.die("Command module %s (%s) must define function '%s'." %
(module.__name__, module.__file__, fn_name))
@@ -99,7 +100,8 @@ def get_module(name):
def get_command(name):
"""Imports the command's function from a module and returns it."""
- return getattr(get_module(name), get_cmd_function_name(name))
+ python_name = get_python_name(name)
+ return getattr(get_module(python_name), python_name)
def parse_specs(args, **kwargs):
diff --git a/lib/spack/spack/main.py b/lib/spack/spack/main.py
index 3ae8403bcf..2d5648f13e 100644
--- a/lib/spack/spack/main.py
+++ b/lib/spack/spack/main.py
@@ -34,6 +34,7 @@ import os
import inspect
import pstats
import argparse
+import tempfile
import llnl.util.tty as tty
from llnl.util.tty.color import *
@@ -236,10 +237,14 @@ class SpackArgumentParser(argparse.ArgumentParser):
def add_command(self, name):
"""Add one subcommand to this parser."""
+ # convert CLI command name to python module name
+ name = spack.cmd.get_python_name(name)
+
# lazily initialize any subparsers
if not hasattr(self, 'subparsers'):
# remove the dummy "command" argument.
- self._remove_action(self._actions[-1])
+ if self._actions[-1].dest == 'command':
+ self._remove_action(self._actions[-1])
self.subparsers = self.add_subparsers(metavar='COMMAND',
dest="command")
@@ -322,7 +327,7 @@ def setup_main_options(args):
def allows_unknown_args(command):
- """This is a basic argument injection test.
+ """Implements really simple argument injection for unknown arguments.
Commands may add an optional argument called "unknown args" to
indicate they can handle unknonwn args, and we'll pass the unknown
@@ -334,7 +339,89 @@ def allows_unknown_args(command):
return (argcount == 3 and varnames[2] == 'unknown_args')
+def _invoke_spack_command(command, parser, args, unknown_args):
+ """Run a spack command *without* setting spack global options."""
+ if allows_unknown_args(command):
+ return_val = command(parser, args, unknown_args)
+ else:
+ if unknown_args:
+ tty.die('unrecognized arguments: %s' % ' '.join(unknown_args))
+ return_val = command(parser, args)
+
+ # Allow commands to return and error code if they want
+ return 0 if return_val is None else return_val
+
+
+class SpackCommand(object):
+ """Callable object that invokes a spack command (for testing).
+
+ Example usage::
+
+ install = SpackCommand('install')
+ install('-v', 'mpich')
+
+ Use this to invoke Spack commands directly from Python and check
+ their stdout and stderr.
+ """
+ def __init__(self, command, fail_on_error=True):
+ """Create a new SpackCommand that invokes ``command`` when called."""
+ self.parser = make_argument_parser()
+ self.parser.add_command(command)
+ self.command_name = command
+ self.command = spack.cmd.get_command(command)
+ self.fail_on_error = fail_on_error
+
+ def __call__(self, *argv):
+ """Invoke this SpackCommand.
+
+ Args:
+ argv (list of str): command line arguments.
+
+ Returns:
+ (str, str): output and error as a strings
+
+ On return, if ``fail_on_error`` is False, return value of comman
+ is set in ``returncode`` property. Otherwise, raise an error.
+ """
+ args, unknown = self.parser.parse_known_args(
+ [self.command_name] + list(argv))
+
+ out, err = sys.stdout, sys.stderr
+ ofd, ofn = tempfile.mkstemp()
+ efd, efn = tempfile.mkstemp()
+
+ try:
+ sys.stdout = open(ofn, 'w')
+ sys.stderr = open(efn, 'w')
+ self.returncode = _invoke_spack_command(
+ self.command, self.parser, args, unknown)
+
+ except SystemExit as e:
+ self.returncode = e.code
+
+ finally:
+ sys.stdout.flush()
+ sys.stdout.close()
+ sys.stderr.flush()
+ sys.stderr.close()
+ sys.stdout, sys.stderr = out, err
+
+ return_out = open(ofn).read()
+ return_err = open(efn).read()
+ os.unlink(ofn)
+ os.unlink(efn)
+
+ if self.fail_on_error and self.returncode != 0:
+ raise SpackCommandError(
+ "Command exited with code %d: %s(%s)" % (
+ self.returncode, self.command_name,
+ ', '.join("'%s'" % a for a in argv)))
+
+ return return_out, return_err
+
+
def _main(command, parser, args, unknown_args):
+ """Run a spack command *and* set spack globaloptions."""
# many operations will fail without a working directory.
set_working_dir()
@@ -345,12 +432,7 @@ def _main(command, parser, args, unknown_args):
# Now actually execute the command
try:
- if allows_unknown_args(command):
- return_val = command(parser, args, unknown_args)
- else:
- if unknown_args:
- tty.die('unrecognized arguments: %s' % ' '.join(unknown_args))
- return_val = command(parser, args)
+ return _invoke_spack_command(command, parser, args, unknown_args)
except SpackError as e:
e.die() # gracefully die on any SpackErrors
except Exception as e:
@@ -361,9 +443,6 @@ def _main(command, parser, args, unknown_args):
sys.stderr.write('\n')
tty.die("Keyboard interrupt.")
- # Allow commands to return and error code if they want
- return 0 if return_val is None else return_val
-
def _profile_wrapper(command, parser, args, unknown_args):
import cProfile
@@ -431,7 +510,7 @@ def main(argv=None):
# Try to load the particular command the caller asked for. If there
# is no module for it, just die.
- command_name = args.command[0].replace('-', '_')
+ command_name = spack.cmd.get_python_name(args.command[0])
try:
parser.add_command(command_name)
except ImportError:
@@ -465,3 +544,7 @@ def main(argv=None):
except SystemExit as e:
return e.code
+
+
+class SpackCommandError(Exception):
+ """Raised when SpackCommand execution fails."""
diff --git a/lib/spack/spack/test/cmd/gpg.py b/lib/spack/spack/test/cmd/gpg.py
index 189c827d05..97f7accd60 100644
--- a/lib/spack/spack/test/cmd/gpg.py
+++ b/lib/spack/spack/test/cmd/gpg.py
@@ -22,13 +22,12 @@
# 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 os
import pytest
import spack
-import spack.cmd.gpg as gpg
import spack.util.gpg as gpg_util
+from spack.main import SpackCommand
from spack.util.executable import ProcessError
@@ -40,6 +39,19 @@ def testing_gpg_directory(tmpdir):
gpg_util.GNUPGHOME = old_gpg_path
+@pytest.fixture(scope='function')
+def mock_gpg_config():
+ orig_gpg_keys_path = spack.gpg_keys_path
+ spack.gpg_keys_path = spack.mock_gpg_keys_path
+ yield
+ spack.gpg_keys_path = orig_gpg_keys_path
+
+
+@pytest.fixture(scope='function')
+def gpg():
+ return SpackCommand('gpg')
+
+
def has_gnupg2():
try:
gpg_util.Gpg.gpg()('--version', output=os.devnull)
@@ -48,45 +60,31 @@ def has_gnupg2():
return False
-@pytest.mark.usefixtures('testing_gpg_directory')
+@pytest.mark.xfail # TODO: fix failing tests.
@pytest.mark.skipif(not has_gnupg2(),
reason='These tests require gnupg2')
-def test_gpg(tmpdir):
- parser = argparse.ArgumentParser()
- gpg.setup_parser(parser)
-
+def test_gpg(gpg, tmpdir, testing_gpg_directory, mock_gpg_config):
# Verify a file with an empty keyring.
- args = parser.parse_args(['verify', os.path.join(
- spack.mock_gpg_data_path, 'content.txt')])
with pytest.raises(ProcessError):
- gpg.gpg(parser, args)
+ gpg('verify', os.path.join(spack.mock_gpg_data_path, 'content.txt'))
# Import the default key.
- args = parser.parse_args(['init'])
- args.import_dir = spack.mock_gpg_keys_path
- gpg.gpg(parser, args)
+ gpg('init')
# List the keys.
# TODO: Test the output here.
- args = parser.parse_args(['list', '--trusted'])
- gpg.gpg(parser, args)
- args = parser.parse_args(['list', '--signing'])
- gpg.gpg(parser, args)
+ gpg('list', '--trusted')
+ gpg('list', '--signing')
# Verify the file now that the key has been trusted.
- args = parser.parse_args(['verify', os.path.join(
- spack.mock_gpg_data_path, 'content.txt')])
- gpg.gpg(parser, args)
+ gpg('verify', os.path.join(spack.mock_gpg_data_path, 'content.txt'))
# Untrust the default key.
- args = parser.parse_args(['untrust', 'Spack testing'])
- gpg.gpg(parser, args)
+ gpg('untrust', 'Spack testing')
# Now that the key is untrusted, verification should fail.
- args = parser.parse_args(['verify', os.path.join(
- spack.mock_gpg_data_path, 'content.txt')])
with pytest.raises(ProcessError):
- gpg.gpg(parser, args)
+ gpg('verify', os.path.join(spack.mock_gpg_data_path, 'content.txt'))
# Create a file to test signing.
test_path = tmpdir.join('to-sign.txt')
@@ -94,88 +92,71 @@ def test_gpg(tmpdir):
fout.write('Test content for signing.\n')
# Signing without a private key should fail.
- args = parser.parse_args(['sign', str(test_path)])
with pytest.raises(RuntimeError) as exc_info:
- gpg.gpg(parser, args)
+ gpg('sign', str(test_path))
assert exc_info.value.args[0] == 'no signing keys are available'
# Create a key for use in the tests.
keypath = tmpdir.join('testing-1.key')
- args = parser.parse_args(['create',
- '--comment', 'Spack testing key',
- '--export', str(keypath),
- 'Spack testing 1',
- 'spack@googlegroups.com'])
- gpg.gpg(parser, args)
+ gpg('create',
+ '--comment', 'Spack testing key',
+ '--export', str(keypath),
+ 'Spack testing 1',
+ 'spack@googlegroups.com')
keyfp = gpg_util.Gpg.signing_keys()[0]
# List the keys.
# TODO: Test the output here.
- args = parser.parse_args(['list', '--trusted'])
- gpg.gpg(parser, args)
- args = parser.parse_args(['list', '--signing'])
- gpg.gpg(parser, args)
+ gpg('list', '--trusted')
+ gpg('list', '--signing')
# Signing with the default (only) key.
- args = parser.parse_args(['sign', str(test_path)])
- gpg.gpg(parser, args)
+ gpg('sign', str(test_path))
# Verify the file we just verified.
- args = parser.parse_args(['verify', str(test_path)])
- gpg.gpg(parser, args)
+ gpg('verify', str(test_path))
# Export the key for future use.
export_path = tmpdir.join('export.testing.key')
- args = parser.parse_args(['export', str(export_path)])
- gpg.gpg(parser, args)
+ gpg('export', str(export_path))
# Create a second key for use in the tests.
- args = parser.parse_args(['create',
- '--comment', 'Spack testing key',
- 'Spack testing 2',
- 'spack@googlegroups.com'])
- gpg.gpg(parser, args)
+ gpg('create',
+ '--comment', 'Spack testing key',
+ 'Spack testing 2',
+ 'spack@googlegroups.com')
# List the keys.
# TODO: Test the output here.
- args = parser.parse_args(['list', '--trusted'])
- gpg.gpg(parser, args)
- args = parser.parse_args(['list', '--signing'])
- gpg.gpg(parser, args)
+ gpg('list', '--trusted')
+ gpg('list', '--signing')
test_path = tmpdir.join('to-sign-2.txt')
with open(str(test_path), 'w+') as fout:
fout.write('Test content for signing.\n')
# Signing with multiple signing keys is ambiguous.
- args = parser.parse_args(['sign', str(test_path)])
with pytest.raises(RuntimeError) as exc_info:
- gpg.gpg(parser, args)
+ gpg('sign', str(test_path))
assert exc_info.value.args[0] == \
'multiple signing keys are available; please choose one'
# Signing with a specified key.
- args = parser.parse_args(['sign', '--key', keyfp, str(test_path)])
- gpg.gpg(parser, args)
+ gpg('sign', '--key', keyfp, str(test_path))
# Untrusting signing keys needs a flag.
- args = parser.parse_args(['untrust', 'Spack testing 1'])
with pytest.raises(ProcessError):
- gpg.gpg(parser, args)
+ gpg('untrust', 'Spack testing 1')
# Untrust the key we created.
- args = parser.parse_args(['untrust', '--signing', keyfp])
- gpg.gpg(parser, args)
+ gpg('untrust', '--signing', keyfp)
# Verification should now fail.
- args = parser.parse_args(['verify', str(test_path)])
with pytest.raises(ProcessError):
- gpg.gpg(parser, args)
+ gpg('verify', str(test_path))
# Trust the exported key.
- args = parser.parse_args(['trust', str(export_path)])
- gpg.gpg(parser, args)
+ gpg('trust', str(export_path))
# Verification should now succeed again.
- args = parser.parse_args(['verify', str(test_path)])
- gpg.gpg(parser, args)
+ gpg('verify', str(test_path))
diff --git a/lib/spack/spack/test/cmd/install.py b/lib/spack/spack/test/cmd/install.py
index 7f9db1baa2..951d9d85d7 100644
--- a/lib/spack/spack/test/cmd/install.py
+++ b/lib/spack/spack/test/cmd/install.py
@@ -22,194 +22,45 @@
# 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 codecs
-import collections
-import contextlib
-import unittest
-from six import StringIO
+from spack.main import SpackCommand
-import llnl.util.filesystem
-import spack
-import spack.cmd
-import spack.cmd.install as install
-FILE_REGISTRY = collections.defaultdict(StringIO)
+install = SpackCommand('install')
-# Monkey-patch open to write module files to a StringIO instance
-@contextlib.contextmanager
-def mock_open(filename, mode, *args):
- if not mode == 'wb':
- message = 'test.test_install : unexpected opening mode for mock_open'
- raise RuntimeError(message)
+def _install_package_and_dependency(
+ tmpdir, builtin_mock, mock_archive, mock_fetch, config,
+ install_mockery):
- FILE_REGISTRY[filename] = StringIO()
+ tmpdir.chdir()
+ install('--log-format=junit', '--log-file=test.xml', 'libdwarf')
- try:
- yield FILE_REGISTRY[filename]
- finally:
- handle = FILE_REGISTRY[filename]
- FILE_REGISTRY[filename] = handle.getvalue()
- handle.close()
+ files = tmpdir.listdir()
+ filename = tmpdir.join('test.xml')
+ assert filename in files
+ content = filename.open().read()
+ assert 'tests="2"' in content
+ assert 'failures="0"' in content
+ assert 'errors="0"' in content
-class MockSpec(object):
- def __init__(self, name, version, hashStr=None):
- self._dependencies = {}
- self.name = name
- self.version = version
- self.hash = hashStr if hashStr else hash((name, version))
+def test_install_package_already_installed(
+ tmpdir, builtin_mock, mock_archive, mock_fetch, config,
+ install_mockery):
- def _deptype_norm(self, deptype):
- if deptype is None:
- return spack.alldeps
- # Force deptype to be a tuple so that we can do set intersections.
- if isinstance(deptype, str):
- return (deptype,)
- return deptype
+ tmpdir.chdir()
+ install('libdwarf')
+ install('--log-format=junit', '--log-file=test.xml', 'libdwarf')
- def _find_deps(self, where, deptype):
- deptype = self._deptype_norm(deptype)
+ files = tmpdir.listdir()
+ filename = tmpdir.join('test.xml')
+ assert filename in files
- return [dep.spec
- for dep in where.values()
- if deptype and any(d in deptype for d in dep.deptypes)]
+ content = filename.open().read()
+ assert 'tests="2"' in content
+ assert 'failures="0"' in content
+ assert 'errors="0"' in content
- def dependencies(self, deptype=None):
- return self._find_deps(self._dependencies, deptype)
-
- def dependents(self, deptype=None):
- return self._find_deps(self._dependents, deptype)
-
- def traverse(self, order=None):
- for _, spec in self._dependencies.items():
- yield spec.spec
- yield self
-
- def dag_hash(self):
- return self.hash
-
- @property
- def short_spec(self):
- return '-'.join([self.name, str(self.version), str(self.hash)])
-
-
-class MockPackage(object):
-
- def __init__(self, spec, buildLogPath):
- self.name = spec.name
- self.spec = spec
- self.installed = False
- self.build_log_path = buildLogPath
-
- def do_install(self, *args, **kwargs):
- for x in self.spec.dependencies():
- x.package.do_install(*args, **kwargs)
- self.installed = True
-
-
-class MockPackageDb(object):
-
- def __init__(self, init=None):
- self.specToPkg = {}
- if init:
- self.specToPkg.update(init)
-
- def get(self, spec):
- return self.specToPkg[spec]
-
-
-def mock_fetch_log(path):
- return []
-
-
-specX = MockSpec('X', '1.2.0')
-specY = MockSpec('Y', '2.3.8')
-specX._dependencies['Y'] = spack.spec.DependencySpec(
- specX, specY, spack.alldeps)
-pkgX = MockPackage(specX, 'logX')
-pkgY = MockPackage(specY, 'logY')
-specX.package = pkgX
-specY.package = pkgY
-
-
-# TODO: add test(s) where Y fails to install
-class InstallTestJunitLog(unittest.TestCase):
- """Tests test-install where X->Y"""
-
- def setUp(self):
- super(InstallTestJunitLog, self).setUp()
- install.PackageBase = MockPackage
- # Monkey patch parse specs
-
- def monkey_parse_specs(x, concretize):
- if x == ['X']:
- return [specX]
- elif x == ['Y']:
- return [specY]
- return []
-
- self.parse_specs = spack.cmd.parse_specs
- spack.cmd.parse_specs = monkey_parse_specs
-
- # Monkey patch os.mkdirp
- self.mkdirp = llnl.util.filesystem.mkdirp
- llnl.util.filesystem.mkdirp = lambda x: True
-
- # Monkey patch open
- self.codecs_open = codecs.open
- codecs.open = mock_open
-
- # Clean FILE_REGISTRY
- FILE_REGISTRY.clear()
-
- pkgX.installed = False
- pkgY.installed = False
-
- # Monkey patch pkgDb
- self.saved_db = spack.repo
- pkgDb = MockPackageDb({specX: pkgX, specY: pkgY})
- spack.repo = pkgDb
-
- def tearDown(self):
- # Remove the monkey patched test_install.open
- codecs.open = self.codecs_open
-
- # Remove the monkey patched os.mkdir
- llnl.util.filesystem.mkdirp = self.mkdirp
- del self.mkdirp
-
- # Remove the monkey patched parse_specs
- spack.cmd.parse_specs = self.parse_specs
- del self.parse_specs
- super(InstallTestJunitLog, self).tearDown()
-
- spack.repo = self.saved_db
-
- def test_installing_both(self):
- parser = argparse.ArgumentParser()
- install.setup_parser(parser)
- args = parser.parse_args(['--log-format=junit', 'X'])
- install.install(parser, args)
- self.assertEqual(len(FILE_REGISTRY), 1)
- for _, content in FILE_REGISTRY.items():
- self.assertTrue('tests="2"' in content)
- self.assertTrue('failures="0"' in content)
- self.assertTrue('errors="0"' in content)
-
- def test_dependency_already_installed(self):
- pkgX.installed = True
- pkgY.installed = True
- parser = argparse.ArgumentParser()
- install.setup_parser(parser)
- args = parser.parse_args(['--log-format=junit', 'X'])
- install.install(parser, args)
- self.assertEqual(len(FILE_REGISTRY), 1)
- for _, content in FILE_REGISTRY.items():
- self.assertTrue('tests="2"' in content)
- self.assertTrue('failures="0"' in content)
- self.assertTrue('errors="0"' in content)
- self.assertEqual(
- sum('skipped' in line for line in content.split('\n')), 2)
+ skipped = [line for line in content.split('\n') if 'skipped' in line]
+ assert len(skipped) == 2
diff --git a/lib/spack/spack/test/cmd/module.py b/lib/spack/spack/test/cmd/module.py
index 8d15afdd0c..b8f24856be 100644
--- a/lib/spack/spack/test/cmd/module.py
+++ b/lib/spack/spack/test/cmd/module.py
@@ -70,15 +70,20 @@ def test_remove_and_add_tcl(database, parser):
# Remove existing modules [tcl]
args = parser.parse_args(['rm', '-y', 'mpileaks'])
module_files = _get_module_files(args)
+
for item in module_files:
assert os.path.exists(item)
+
module.module(parser, args)
+
for item in module_files:
assert not os.path.exists(item)
# Add them back [tcl]
args = parser.parse_args(['refresh', '-y', 'mpileaks'])
+
module.module(parser, args)
+
for item in module_files:
assert os.path.exists(item)
diff --git a/lib/spack/spack/test/cmd/python.py b/lib/spack/spack/test/cmd/python.py
index 5ad8456e49..5e3ea83053 100644
--- a/lib/spack/spack/test/cmd/python.py
+++ b/lib/spack/spack/test/cmd/python.py
@@ -22,22 +22,12 @@
# 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
+from spack.main import SpackCommand
-from spack.cmd.python import *
+python = SpackCommand('python')
-@pytest.fixture(scope='module')
-def parser():
- """Returns the parser for the ``python`` command"""
- parser = argparse.ArgumentParser()
- setup_parser(parser)
- return parser
-
-
-def test_python(parser):
- args = parser.parse_args([
- '-c', 'import spack; print(spack.spack_version)'
- ])
- python(parser, args)
+def test_python():
+ out, err = python('-c', 'import spack; print(spack.spack_version)')
+ assert out.strip() == str(spack.spack_version)
diff --git a/lib/spack/spack/test/cmd/uninstall.py b/lib/spack/spack/test/cmd/uninstall.py
index a47c76715b..72dda23f93 100644
--- a/lib/spack/spack/test/cmd/uninstall.py
+++ b/lib/spack/spack/test/cmd/uninstall.py
@@ -24,7 +24,9 @@
##############################################################################
import pytest
import spack.store
-import spack.cmd.uninstall
+from spack.main import SpackCommand, SpackCommandError
+
+uninstall = SpackCommand('uninstall')
class MockArgs(object):
@@ -37,20 +39,21 @@ class MockArgs(object):
self.yes_to_all = True
-def test_uninstall(database):
- parser = None
- uninstall = spack.cmd.uninstall.uninstall
- # Multiple matches
- args = MockArgs(['mpileaks'])
- with pytest.raises(SystemExit):
- uninstall(parser, args)
- # Installed dependents
- args = MockArgs(['libelf'])
- with pytest.raises(SystemExit):
- uninstall(parser, args)
- # Recursive uninstall
- args = MockArgs(['callpath'], all=True, dependents=True)
- uninstall(parser, args)
+def test_multiple_matches(database):
+ """Test unable to uninstall when multiple matches."""
+ with pytest.raises(SpackCommandError):
+ uninstall('-y', 'mpileaks')
+
+
+def test_installed_dependents(database):
+ """Test can't uninstall when ther are installed dependents."""
+ with pytest.raises(SpackCommandError):
+ uninstall('-y', 'libelf')
+
+
+def test_recursive_uninstall(database):
+ """Test recursive uninstall."""
+ uninstall('-y', '-a', '--dependents', 'callpath')
all_specs = spack.store.layout.all_specs()
assert len(all_specs) == 8
diff --git a/lib/spack/spack/test/cmd/url.py b/lib/spack/spack/test/cmd/url.py
index ae585b328f..21f88e928b 100644
--- a/lib/spack/spack/test/cmd/url.py
+++ b/lib/spack/spack/test/cmd/url.py
@@ -22,18 +22,13 @@
# 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 re
import pytest
-
+from spack.url import UndetectableVersionError
+from spack.main import SpackCommand
from spack.cmd.url import *
-
-@pytest.fixture(scope='module')
-def parser():
- """Returns the parser for the ``url`` command"""
- parser = argparse.ArgumentParser()
- setup_parser(parser)
- return parser
+url = SpackCommand('url')
class MyPackage:
@@ -77,51 +72,64 @@ def test_version_parsed_correctly():
assert not version_parsed_correctly(MyPackage('', ['0.18.0']), 'oce-0.18.0') # noqa
-def test_url_parse(parser):
- args = parser.parse_args(['parse', 'http://zlib.net/fossils/zlib-1.2.10.tar.gz'])
- url(parser, args)
+def test_url_parse():
+ url('parse', 'http://zlib.net/fossils/zlib-1.2.10.tar.gz')
-@pytest.mark.xfail
-def test_url_parse_xfail(parser):
+def test_url_with_no_version_fails():
# No version in URL
- args = parser.parse_args(['parse', 'http://www.netlib.org/voronoi/triangle.zip'])
- url(parser, args)
+ with pytest.raises(UndetectableVersionError):
+ url('parse', 'http://www.netlib.org/voronoi/triangle.zip')
-def test_url_list(parser):
- args = parser.parse_args(['list'])
- total_urls = url_list(args)
+def test_url_list():
+ out, err = url('list')
+ total_urls = len(out.split('\n'))
# The following two options should not change the number of URLs printed.
- args = parser.parse_args(['list', '--color', '--extrapolation'])
- colored_urls = url_list(args)
+ out, err = url('list', '--color', '--extrapolation')
+ colored_urls = len(out.split('\n'))
assert colored_urls == total_urls
# The following options should print fewer URLs than the default.
# If they print the same number of URLs, something is horribly broken.
# If they say we missed 0 URLs, something is probably broken too.
- args = parser.parse_args(['list', '--incorrect-name'])
- incorrect_name_urls = url_list(args)
+ out, err = url('list', '--incorrect-name')
+ incorrect_name_urls = len(out.split('\n'))
assert 0 < incorrect_name_urls < total_urls
- args = parser.parse_args(['list', '--incorrect-version'])
- incorrect_version_urls = url_list(args)
+ out, err = url('list', '--incorrect-version')
+ incorrect_version_urls = len(out.split('\n'))
assert 0 < incorrect_version_urls < total_urls
- args = parser.parse_args(['list', '--correct-name'])
- correct_name_urls = url_list(args)
+ out, err = url('list', '--correct-name')
+ correct_name_urls = len(out.split('\n'))
assert 0 < correct_name_urls < total_urls
- args = parser.parse_args(['list', '--correct-version'])
- correct_version_urls = url_list(args)
+ out, err = url('list', '--correct-version')
+ correct_version_urls = len(out.split('\n'))
assert 0 < correct_version_urls < total_urls
-def test_url_summary(parser):
- args = parser.parse_args(['summary'])
+def test_url_summary():
+ """Test the URL summary command."""
+ # test url_summary, the internal function that does the work
(total_urls, correct_names, correct_versions,
- name_count_dict, version_count_dict) = url_summary(args)
+ name_count_dict, version_count_dict) = url_summary(None)
assert 0 < correct_names <= sum(name_count_dict.values()) <= total_urls # noqa
assert 0 < correct_versions <= sum(version_count_dict.values()) <= total_urls # noqa
+
+ # make sure it agrees with the actual command.
+ out, err = url('summary')
+ out_total_urls = int(
+ re.search(r'Total URLs found:\s*(\d+)', out).group(1))
+ assert out_total_urls == total_urls
+
+ out_correct_names = int(
+ re.search(r'Names correctly parsed:\s*(\d+)', out).group(1))
+ assert out_correct_names == correct_names
+
+ out_correct_versions = int(
+ re.search(r'Versions correctly parsed:\s*(\d+)', out).group(1))
+ assert out_correct_versions == correct_versions
diff --git a/lib/spack/spack/test/conftest.py b/lib/spack/spack/test/conftest.py
index c23fb466a5..e0a745c7e9 100644
--- a/lib/spack/spack/test/conftest.py
+++ b/lib/spack/spack/test/conftest.py
@@ -35,16 +35,18 @@ import ordereddict_backport
import py
import pytest
+
import spack
import spack.architecture
import spack.database
import spack.directory_layout
-import spack.fetch_strategy
import spack.platforms.test
import spack.repository
import spack.stage
import spack.util.executable
import spack.util.pattern
+from spack.package import PackageBase
+from spack.fetch_strategy import *
##########
@@ -78,12 +80,10 @@ def mock_fetch_cache(monkeypatch):
pass
def fetch(self):
- raise spack.fetch_strategy.FetchError(
- 'Mock cache always fails for tests'
- )
+ raise FetchError('Mock cache always fails for tests')
def __str__(self):
- return "[mock fetcher]"
+ return "[mock fetch cache]"
monkeypatch.setattr(spack, 'fetch_cache', MockCache())
@@ -287,6 +287,43 @@ def refresh_db_on_exit(database):
yield
database.refresh()
+
+@pytest.fixture()
+def install_mockery(tmpdir, config, builtin_mock):
+ """Hooks a fake install directory and a fake db into Spack."""
+ layout = spack.store.layout
+ db = spack.store.db
+ # Use a fake install directory to avoid conflicts bt/w
+ # installed pkgs and mock packages.
+ spack.store.layout = spack.directory_layout.YamlDirectoryLayout(
+ str(tmpdir))
+ spack.store.db = spack.database.Database(str(tmpdir))
+ # We use a fake package, so skip the checksum.
+ spack.do_checksum = False
+ yield
+ # Turn checksumming back on
+ spack.do_checksum = True
+ # Restore Spack's layout.
+ spack.store.layout = layout
+ spack.store.db = db
+
+
+@pytest.fixture()
+def mock_fetch(mock_archive):
+ """Fake the URL for a package so it downloads from a file."""
+ fetcher = FetchStrategyComposite()
+ fetcher.append(URLFetchStrategy(mock_archive.url))
+
+ @property
+ def fake_fn(self):
+ return fetcher
+
+ orig_fn = PackageBase.fetcher
+ PackageBase.fetcher = fake_fn
+ yield
+ PackageBase.fetcher = orig_fn
+
+
##########
# Fake archives and repositories
##########
diff --git a/lib/spack/spack/test/install.py b/lib/spack/spack/test/install.py
index 3a934d5ea2..278b2efb70 100644
--- a/lib/spack/spack/test/install.py
+++ b/lib/spack/spack/test/install.py
@@ -22,45 +22,15 @@
# 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 os
import pytest
+
import spack
import spack.store
-from spack.database import Database
-from spack.directory_layout import YamlDirectoryLayout
-from spack.fetch_strategy import URLFetchStrategy, FetchStrategyComposite
from spack.spec import Spec
-import os
-
-@pytest.fixture()
-def install_mockery(tmpdir, config, builtin_mock):
- """Hooks a fake install directory and a fake db into Spack."""
- layout = spack.store.layout
- db = spack.store.db
- # Use a fake install directory to avoid conflicts bt/w
- # installed pkgs and mock packages.
- spack.store.layout = YamlDirectoryLayout(str(tmpdir))
- spack.store.db = Database(str(tmpdir))
- # We use a fake package, so skip the checksum.
- spack.do_checksum = False
- yield
- # Turn checksumming back on
- spack.do_checksum = True
- # Restore Spack's layout.
- spack.store.layout = layout
- spack.store.db = db
-
-
-def fake_fetchify(url, pkg):
- """Fake the URL for a package so it downloads from a file."""
- fetcher = FetchStrategyComposite()
- fetcher.append(URLFetchStrategy(url))
- pkg.fetcher = fetcher
-
-
-@pytest.mark.usefixtures('install_mockery')
-def test_install_and_uninstall(mock_archive):
+def test_install_and_uninstall(install_mockery, mock_fetch):
# Get a basic concrete spec for the trivial install package.
spec = Spec('trivial-install-test-package')
spec.concretize()
@@ -69,8 +39,6 @@ def test_install_and_uninstall(mock_archive):
# Get the package
pkg = spack.repo.get(spec)
- fake_fetchify(mock_archive.url, pkg)
-
try:
pkg.do_install()
pkg.do_uninstall()
@@ -114,12 +82,10 @@ class MockStage(object):
return getattr(self.wrapped_stage, attr)
-@pytest.mark.usefixtures('install_mockery')
-def test_partial_install_delete_prefix_and_stage(mock_archive):
+def test_partial_install_delete_prefix_and_stage(install_mockery, mock_fetch):
spec = Spec('canfail')
spec.concretize()
pkg = spack.repo.get(spec)
- fake_fetchify(mock_archive.url, pkg)
remove_prefix = spack.package.Package.remove_prefix
instance_rm_prefix = pkg.remove_prefix
@@ -145,14 +111,12 @@ def test_partial_install_delete_prefix_and_stage(mock_archive):
pass
-@pytest.mark.usefixtures('install_mockery')
-def test_partial_install_keep_prefix(mock_archive):
+def test_partial_install_keep_prefix(install_mockery, mock_fetch):
spec = Spec('canfail')
spec.concretize()
pkg = spack.repo.get(spec)
# Normally the stage should start unset, but other tests set it
pkg._stage = None
- fake_fetchify(mock_archive.url, pkg)
remove_prefix = spack.package.Package.remove_prefix
try:
# If remove_prefix is called at any point in this test, that is an
@@ -175,12 +139,10 @@ def test_partial_install_keep_prefix(mock_archive):
pass
-@pytest.mark.usefixtures('install_mockery')
-def test_second_install_no_overwrite_first(mock_archive):
+def test_second_install_no_overwrite_first(install_mockery, mock_fetch):
spec = Spec('canfail')
spec.concretize()
pkg = spack.repo.get(spec)
- fake_fetchify(mock_archive.url, pkg)
remove_prefix = spack.package.Package.remove_prefix
try:
spack.package.Package.remove_prefix = mock_remove_prefix
@@ -198,28 +160,14 @@ def test_second_install_no_overwrite_first(mock_archive):
pass
-@pytest.mark.usefixtures('install_mockery')
-def test_store(mock_archive):
+def test_store(install_mockery, mock_fetch):
spec = Spec('cmake-client').concretized()
-
- for s in spec.traverse():
- fake_fetchify(mock_archive.url, s.package)
-
pkg = spec.package
- try:
- pkg.do_install()
- except Exception:
- pkg.remove_prefix()
- raise
+ pkg.do_install()
-@pytest.mark.usefixtures('install_mockery')
-def test_failing_build(mock_archive):
+def test_failing_build(install_mockery, mock_fetch):
spec = Spec('failing-build').concretized()
-
- for s in spec.traverse():
- fake_fetchify(mock_archive.url, s.package)
-
pkg = spec.package
with pytest.raises(spack.build_environment.ChildError):
pkg.do_install()
diff --git a/var/spack/repos/builtin.mock/packages/a/package.py b/var/spack/repos/builtin.mock/packages/a/package.py
index 59d8b9e330..e2fa6c35b0 100644
--- a/var/spack/repos/builtin.mock/packages/a/package.py
+++ b/var/spack/repos/builtin.mock/packages/a/package.py
@@ -26,7 +26,7 @@ from spack import *
class A(AutotoolsPackage):
- """Simple package with no dependencies"""
+ """Simple package with one optional dependency"""
homepage = "http://www.example.com"
url = "http://www.example.com/a-1.0.tar.gz"
diff --git a/var/spack/repos/builtin.mock/packages/libdwarf/package.py b/var/spack/repos/builtin.mock/packages/libdwarf/package.py
index 10d6fa8e88..0cdbaf2a33 100644
--- a/var/spack/repos/builtin.mock/packages/libdwarf/package.py
+++ b/var/spack/repos/builtin.mock/packages/libdwarf/package.py
@@ -41,4 +41,4 @@ class Libdwarf(Package):
depends_on("libelf")
def install(self, spec, prefix):
- pass
+ touch(prefix.libdwarf)
diff --git a/var/spack/repos/builtin.mock/packages/libelf/package.py b/var/spack/repos/builtin.mock/packages/libelf/package.py
index 3a2fe603ef..0390963081 100644
--- a/var/spack/repos/builtin.mock/packages/libelf/package.py
+++ b/var/spack/repos/builtin.mock/packages/libelf/package.py
@@ -34,11 +34,4 @@ class Libelf(Package):
version('0.8.10', '9db4d36c283d9790d8fa7df1f4d7b4d9')
def install(self, spec, prefix):
- configure("--prefix=%s" % prefix,
- "--enable-shared",
- "--disable-dependency-tracking",
- "--disable-debug")
- make()
-
- # The mkdir commands in libelf's intsall can fail in parallel
- make("install", parallel=False)
+ touch(prefix.libelf)