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
This commit is contained in:
Todd Gamblin 2017-07-22 21:27:54 -07:00 committed by GitHub
parent c07d93a3e5
commit f159246d1d
13 changed files with 299 additions and 398 deletions

View File

@ -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):

View File

@ -34,6 +34,7 @@
import inspect
import pstats
import argparse
import tempfile
import llnl.util.tty as tty
from llnl.util.tty.color import *
@ -236,10 +237,14 @@ def add_subcommand_group(title, commands):
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."""

View File

@ -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))

View File

@ -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
import llnl.util.filesystem
import spack
import spack.cmd
import spack.cmd.install as install
FILE_REGISTRY = collections.defaultdict(StringIO)
from spack.main import SpackCommand
# 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)
FILE_REGISTRY[filename] = StringIO()
try:
yield FILE_REGISTRY[filename]
finally:
handle = FILE_REGISTRY[filename]
FILE_REGISTRY[filename] = handle.getvalue()
handle.close()
install = SpackCommand('install')
class MockSpec(object):
def _install_package_and_dependency(
tmpdir, builtin_mock, mock_archive, mock_fetch, config,
install_mockery):
def __init__(self, name, version, hashStr=None):
self._dependencies = {}
self.name = name
self.version = version
self.hash = hashStr if hashStr else hash((name, version))
tmpdir.chdir()
install('--log-format=junit', '--log-file=test.xml', 'libdwarf')
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
files = tmpdir.listdir()
filename = tmpdir.join('test.xml')
assert filename in files
def _find_deps(self, where, deptype):
deptype = self._deptype_norm(deptype)
return [dep.spec
for dep in where.values()
if deptype and any(d in deptype for d in dep.deptypes)]
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)])
content = filename.open().read()
assert 'tests="2"' in content
assert 'failures="0"' in content
assert 'errors="0"' in content
class MockPackage(object):
def test_install_package_already_installed(
tmpdir, builtin_mock, mock_archive, mock_fetch, config,
install_mockery):
def __init__(self, spec, buildLogPath):
self.name = spec.name
self.spec = spec
self.installed = False
self.build_log_path = buildLogPath
tmpdir.chdir()
install('libdwarf')
install('--log-format=junit', '--log-file=test.xml', 'libdwarf')
def do_install(self, *args, **kwargs):
for x in self.spec.dependencies():
x.package.do_install(*args, **kwargs)
self.installed = True
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 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

View File

@ -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)

View File

@ -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)

View File

@ -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 @@ def __init__(self, packages, all=False, force=False, dependents=False):
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

View File

@ -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

View File

@ -35,16 +35,18 @@
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 set_stage(self, stage):
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
##########

View File

@ -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 @@ def __getattr__(self, attr):
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()

View File

@ -26,7 +26,7 @@
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"

View File

@ -41,4 +41,4 @@ class Libdwarf(Package):
depends_on("libelf")
def install(self, spec, prefix):
pass
touch(prefix.libdwarf)

View File

@ -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)