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:
parent
c07d93a3e5
commit
f159246d1d
@ -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):
|
||||
|
@ -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."""
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
##########
|
||||
|
@ -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()
|
||||
|
@ -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"
|
||||
|
@ -41,4 +41,4 @@ class Libdwarf(Package):
|
||||
depends_on("libelf")
|
||||
|
||||
def install(self, spec, prefix):
|
||||
pass
|
||||
touch(prefix.libdwarf)
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user