def parse_specs(args, **kwargs): """Convenience function for parsing arguments from specs. Handles common exceptions and dies if there are errors. """ concretize = kwargs.get('concretize', False) normalize = kwargs.get('normalize', False) tests = kwargs.get('tests', False) try: specs = spack.spec.parse(args) for spec in specs: if concretize: spec.concretize(tests=tests) # implies normalize elif normalize: spec.normalize(tests=tests) return specs except spack.spec.SpecParseError as e: msg = e.message + "\n" + str(e.string) + "\n" msg += (e.pos + 2) * " " + "^" raise SpackError(msg) except spack.spec.SpecError as e: msg = e.message if e.long_message: msg += e.long_message raise SpackError(msg)
def test_suffixes(self, tcl_factory): spack.modules._module_config = self.configuration_suffix spec = spack.spec.Spec('mpileaks+debug arch=x86-linux') spec.concretize() generator = tcl_factory(spec) assert 'foo' in generator.use_name spec = spack.spec.Spec('mpileaks~debug arch=x86-linux') spec.concretize() generator = tcl_factory(spec) assert 'bar' in generator.use_name
def _impl(spec_str): # Write the module file spec = spack.spec.Spec(spec_str) spec.concretize() generator = writer_cls(spec) generator.write() # Get its filename filename = generator.layout.filename # Retrieve the content content = filename_dict[filename].split('\n') generator.remove() return content
def test_setup_environment(self, tcl_factory): spec = spack.spec.Spec('mpileaks') spec.concretize() content = get_modulefile_content(tcl_factory, spec) assert len([x for x in content if 'setenv FOOBAR' in x]) == 1 assert len( [x for x in content if 'setenv FOOBAR "mpileaks"' in x] ) == 1 content = get_modulefile_content(tcl_factory, spec['callpath']) assert len([x for x in content if 'setenv FOOBAR' in x]) == 1 assert len( [x for x in content if 'setenv FOOBAR "callpath"' in x] ) == 1
def get_modulefile_content(factory, spec): """Writes the module file and returns the content as a string. :param factory: module file factory :param spec: spec of the module file to be written :return: content of the module file :rtype: str """ spec.concretize() generator = factory(spec) generator.write() content = FILE_REGISTRY[generator.file_name].split('\n') generator.remove() return content
def create_installer(spec_name): """ Create an installer for the named spec Args: spec_name (str): Name of the explicit install spec Return: spec (Spec): concretized spec installer (PackageInstaller): the associated package installer """ spec = spack.spec.Spec(spec_name) spec.concretize() assert spec.concrete return spec, inst.PackageInstaller(spec.package)
def test_process_external_package_module(install_mockery, monkeypatch, capfd): """Test to simply cover the external module message path.""" spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete # Ensure take the external module path WITHOUT any changes to the database monkeypatch.setattr(spack.database.Database, 'get_record', _none) spec.external_path = '/actual/external/path/not/checked' spec.external_modules = ['unchecked_module'] inst._process_external_package(spec.package, False) out = capfd.readouterr()[0] assert 'has external module in {0}'.format(spec.external_modules) in out
def test_spack_monitor_send_phase(mock_monitor_request, install_mockery, install_mockery_mutable_config): monitor = get_client(host="hostname") def get_build_id(*args, **kwargs): return 1 spec = spack.spec.Spec("dttop") spec.concretize() response = monitor.send_phase(spec.package, "autoconf", spec.package.install_log_path, "SUCCESS") assert "message" in response and "data" in response and "code" in response assert response['code'] == 200
def test_spack_monitor_send_analyze_metadata(monkeypatch, mock_monitor_request, install_mockery, install_mockery_mutable_config): def buildid(*args, **kwargs): return 1 monkeypatch.setattr(spack.monitor.SpackMonitorClient, "get_build_id", buildid) monitor = get_client(host="hostname") spec = spack.spec.Spec("dttop") spec.concretize() response = monitor.send_analyze_metadata(spec.package, metadata={"boop": "beep"}) assert "message" in response and "data" in response and "code" in response assert response['code'] == 200
def test_dev_build_before(tmpdir, mock_packages, install_mockery): spec = spack.spec.Spec('[email protected] dev_path=%s' % tmpdir) spec.concretize() with tmpdir.as_cwd(): with open(spec.package.filename, 'w') as f: f.write(spec.package.original_string) dev_build('-b', 'edit', '[email protected]') assert spec.package.filename in os.listdir(os.getcwd()) with open(spec.package.filename, 'r') as f: assert f.read() == spec.package.original_string assert not os.path.exists(spec.prefix)
def test_dev_build_basics(tmpdir, mock_packages, install_mockery): spec = spack.spec.Spec('[email protected] dev_path=%s' % tmpdir) spec.concretize() with tmpdir.as_cwd(): with open(spec.package.filename, 'w') as f: f.write(spec.package.original_string) dev_build('[email protected]') assert spec.package.filename in os.listdir(spec.prefix) with open(os.path.join(spec.prefix, spec.package.filename), 'r') as f: assert f.read() == spec.package.replacement_string assert os.path.exists(str(tmpdir))
def spec(parser, args): name_fmt = '{namespace}.{name}' if args.namespaces else '{name}' fmt = '{@version}{%compiler}{compiler_flags}{variants}{arch=architecture}' install_status_fn = spack.spec.Spec.install_status kwargs = { 'cover': args.cover, 'format': name_fmt + fmt, 'hashlen': None if args.very_long else 7, 'show_types': args.types, 'status_fn': install_status_fn if args.install_status else None } # use a read transaction if we are getting install status for every # spec in the DAG. This avoids repeatedly querying the DB. tree_context = nullcontext if args.install_status: tree_context = spack.store.db.read_transaction if not args.specs: tty.die("spack spec requires at least one spec") for spec in spack.cmd.parse_specs(args.specs): # With -y, just print YAML to output. if args.format: if spec.name in spack.repo.path or spec.virtual: spec.concretize() # The user can specify the hash type to use hash_type = getattr(ht, args.hash_type) if args.format == 'yaml': # use write because to_yaml already has a newline. sys.stdout.write(spec.to_yaml(hash=hash_type)) else: print(spec.to_json(hash=hash_type)) continue with tree_context(): kwargs['hashes'] = False # Always False for input spec print("Input spec") print("--------------------------------") print(spec.tree(**kwargs)) kwargs['hashes'] = args.long or args.very_long print("Concretized") print("--------------------------------") spec.concretize() print(spec.tree(**kwargs))
def get_modulefile_content(factory, spec): """Writes the module file and returns the content as a string. Args: factory: module file factory spec: spec of the module file to be written Returns: str: content of the module file """ spec.concretize() generator = factory(spec) generator.write() content = FILE_REGISTRY[generator.file_name].split('\n') generator.remove() return content
def test_build_task_basics(install_mockery): spec = spack.spec.Spec('dependent-install') spec.concretize() assert spec.concrete # Ensure key properties match expectations task = inst.BuildTask(spec.package, False, 0, 0, inst.STATUS_ADDED, []) assert task.priority == len(task.uninstalled_deps) assert task.key == (task.priority, task.sequence) # Ensure flagging installed works as expected assert len(task.uninstalled_deps) > 0 assert task.dependencies == task.uninstalled_deps task.flag_installed(task.dependencies) assert len(task.uninstalled_deps) == 0 assert task.priority == 0
def test_packages_needed_to_bootstrap_compiler_packages( install_mockery, monkeypatch): spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() def _conc_spec(compiler): return spack.spec.Spec('a').concretized() # Ensure we can get past functions that are precluding obtaining # packages. monkeypatch.setattr(spack.compilers, 'compilers_for_spec', _none) monkeypatch.setattr(spack.compilers, 'pkg_spec_for_compiler', _conc_spec) monkeypatch.setattr(spack.spec.Spec, 'concretize', _noop) packages = inst._packages_needed_to_bootstrap_compiler(spec.package) assert packages
def _impl(spec_str, module_set_name='default'): # Write the module file spec = spack.spec.Spec(spec_str) spec.concretize() generator = writer_cls(spec, module_set_name) generator.write(overwrite=True) # Get its filename filename = generator.layout.filename # Retrieve the content with open(filename) as f: content = f.readlines() content = ''.join(content).split('\n') generator.remove() return content
def test_dev_build_env_dependency(tmpdir, mock_packages, install_mockery, mock_fetch, mutable_mock_env_path): """ Test non-root specs in an environment are properly marked for dev builds. """ # setup dev-build-test-install package for dev build build_dir = tmpdir.mkdir('build') spec = spack.spec.Spec('[email protected]') dep_spec = spack.spec.Spec('dev-build-test-install') with build_dir.as_cwd(): dep_pkg_cls = spack.repo.path.get_pkg_class(dep_spec.name) with open(dep_pkg_cls.filename, 'w') as f: f.write(dep_pkg_cls.original_string) # setup environment envdir = tmpdir.mkdir('env') with envdir.as_cwd(): with open('spack.yaml', 'w') as f: f.write("""\ env: specs: - [email protected] develop: dev-build-test-install: spec: [email protected] path: %s """ % os.path.relpath(str(build_dir), start=str(envdir))) env('create', 'test', './spack.yaml') with ev.read('test'): # concretize in the environment to get the dev build info # equivalent to setting dev_build and dev_path variants # on all specs above spec.concretize() dep_spec.concretize() install() # Ensure that both specs installed properly assert dep_spec.package.filename in os.listdir(dep_spec.prefix) assert os.path.exists(spec.prefix) # Ensure variants set properly; ensure build_dir is absolute and normalized for dep in (dep_spec, spec['dev-build-test-install']): assert dep.satisfies('dev_path=%s' % build_dir) assert spec.satisfies('^dev_path=*')
def test_dev_build_rebuild_on_source_changes(test_spec, tmpdir, mock_packages, install_mockery, mutable_mock_env_path, mock_fetch): """Test dev builds rebuild on changes to source code. ``test_spec = dev-build-test-install`` tests rebuild for changes to package ``test_spec = dependent-of-dev-build`` tests rebuild for changes to dep """ # setup dev-build-test-install package for dev build build_dir = tmpdir.mkdir('build') spec = spack.spec.Spec('[email protected] dev_path=%s' % build_dir) spec.concretize() def reset_string(): with build_dir.as_cwd(): with open(spec.package.filename, 'w') as f: f.write(spec.package.original_string) reset_string() # setup environment envdir = tmpdir.mkdir('env') with envdir.as_cwd(): with open('spack.yaml', 'w') as f: f.write("""\ env: specs: - %[email protected] develop: dev-build-test-install: spec: [email protected] path: %s """ % (test_spec, build_dir)) env('create', 'test', './spack.yaml') with ev.read('test'): install() reset_string() # so the package will accept rebuilds fs.touch(os.path.join(str(build_dir), 'test')) output = install() assert 'Installing %s' % test_spec in output
def installer_args(spec_names, kwargs={}): """Return a the installer argument with each spec paired with kwargs Args: spec_names (list of str): list of spec names kwargs (dict or None): install arguments to apply to all of the specs Returns: list of (spec, kwargs): the installer constructor argument """ arg = [] for name in spec_names: spec = spack.spec.Spec(name) spec.concretize() assert spec.concrete arg.append((spec, kwargs)) return arg
def test_try_install_from_binary_cache(install_mockery, mock_packages, monkeypatch, capsys): """Tests SystemExit path for_try_install_from_binary_cache.""" def _spec(spec, force): spec = spack.spec.Spec('mpi').concretized() return {spec: None} spec = spack.spec.Spec('mpich') spec.concretize() monkeypatch.setattr(spack.binary_distribution, 'get_spec', _spec) with pytest.raises(SystemExit): inst._try_install_from_binary_cache(spec.package, False, False) captured = capsys.readouterr() assert 'add a spack mirror to allow download' in str(captured)
def test_setup_environment(self, modulefile_content, module_configuration): """Tests the internal set-up of run-time environment.""" module_configuration('suffix') content = modulefile_content('mpileaks') assert len([x for x in content if 'setenv FOOBAR' in x]) == 1 assert len([x for x in content if 'setenv FOOBAR "mpileaks"' in x]) == 1 spec = spack.spec.Spec('mpileaks') spec.concretize() content = modulefile_content(str(spec['callpath'])) assert len([x for x in content if 'setenv FOOBAR' in x]) == 1 assert len([x for x in content if 'setenv FOOBAR "callpath"' in x]) == 1
def test_no_hash(self): # Make sure that virtual providers (in the hierarchy) always # include a hash. Make sure that the module file for the spec # does not include a hash if hash_length is 0. spack.modules._module_config = self.configuration_no_hash spec = spack.spec.Spec(mpileaks_spec_string) spec.concretize() module = spack.modules.LmodModule(spec) path = module.file_name mpiSpec = spec['mpi'] mpiElement = "{0}/{1}-{2}/".format(mpiSpec.name, mpiSpec.version, mpiSpec.dag_hash(length=7)) self.assertTrue(mpiElement in path) mpileaksSpec = spec mpileaksElement = "{0}/{1}.lua".format(mpileaksSpec.name, mpileaksSpec.version) self.assertTrue(path.endswith(mpileaksElement))
def test_build_task_errors(install_mockery): with pytest.raises(ValueError, match='must be a package'): inst.BuildTask('abc', None, False, 0, 0, 0, []) pkg = spack.repo.get('trivial-install-test-package') with pytest.raises(ValueError, match='must have a concrete spec'): inst.BuildTask(pkg, None, False, 0, 0, 0, []) spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete with pytest.raises(ValueError, match='must have a build request'): inst.BuildTask(spec.package, None, False, 0, 0, 0, []) request = inst.BuildRequest(spec.package, {}) with pytest.raises(inst.InstallError, match='Cannot create a build task'): inst.BuildTask(spec.package, request, False, 0, 0, inst.STATUS_REMOVED, [])
def test_install_from_cache_errors(install_mockery, capsys): """Test to ensure cover _install_from_cache errors.""" spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete # Check with cache-only with pytest.raises(SystemExit): inst._install_from_cache(spec.package, True, True, False) captured = str(capsys.readouterr()) assert 'No binary' in captured assert 'found when cache-only specified' in captured assert not spec.package.installed_from_binary_cache # Check when don't expect to install only from binary cache assert not inst._install_from_cache(spec.package, False, True, False) assert not spec.package.installed_from_binary_cache
def test_dev_build_until_last_phase(tmpdir, mock_packages, install_mockery): # Test that we ignore the last_phase argument if it is already last spec = spack.spec.Spec('[email protected] dev_path=%s' % tmpdir) spec.concretize() with tmpdir.as_cwd(): with open(spec.package.filename, 'w') as f: f.write(spec.package.original_string) dev_build('-u', 'install', '[email protected]') assert spec.package.filename in os.listdir(os.getcwd()) with open(spec.package.filename, 'r') as f: assert f.read() == spec.package.replacement_string assert os.path.exists(spec.prefix) assert spack.store.db.query(spec, installed=True) assert os.path.exists(str(tmpdir))
def test_build_request_strings(install_mockery): """Tests of BuildRequest repr and str for coverage purposes.""" # Using a package with one dependency spec = spack.spec.Spec('dependent-install') spec.concretize() assert spec.concrete # Ensure key properties match expectations request = inst.BuildRequest(spec.package, {}) # Cover __repr__ irep = request.__repr__() assert irep.startswith(request.__class__.__name__) # Cover __str__ istr = str(request) assert "package=dependent-install" in istr assert "install_args=" in istr
def test_packages_needed_to_bootstrap_compiler(install_mockery, monkeypatch): """Test to cover most of _packages_needed_to_boostrap_compiler.""" # TODO: More work is needed to go beyond the dependency check def _no_compilers(pkg, arch_spec): return [] # Test path where no compiler packages returned spec = spack.spec.Spec('trivial-install-test-package') spec.concretize() assert spec.concrete packages = inst._packages_needed_to_bootstrap_compiler(spec.package) assert not packages # Test up to the dependency check monkeypatch.setattr(spack.compilers, 'compilers_for_spec', _no_compilers) with pytest.raises(spack.repo.UnknownPackageError, matches='not found'): inst._packages_needed_to_bootstrap_compiler(spec.package)
def test_no_hash(self, lmod_factory): # Make sure that virtual providers (in the hierarchy) always # include a hash. Make sure that the module file for the spec # does not include a hash if hash_length is 0. spack.modules._module_config = self.configuration_no_hash spec = spack.spec.Spec(mpileaks_spec_string) spec.concretize() module = lmod_factory(spec) path = module.file_name mpi_spec = spec['mpi'] mpiElement = "{0}/{1}-{2}/".format( mpi_spec.name, mpi_spec.version, mpi_spec.dag_hash(length=7) ) assert mpiElement in path mpileaks_spec = spec mpileaks_element = "{0}/{1}.lua".format( mpileaks_spec.name, mpileaks_spec.version) assert path.endswith(mpileaks_element)
def parse_specs(args, **kwargs): """Convenience function for parsing arguments from specs. Handles common exceptions and dies if there are errors. """ concretize = kwargs.get('concretize', False) normalize = kwargs.get('normalize', False) tests = kwargs.get('tests', False) try: sargs = args if not isinstance(args, six.string_types): if len(sargs) == 1: # a single argument contains a full spec and will confuse the # Parser if escaped -> simply take it out of the list if not isinstance(sargs, type([])): # Since the list keyword is taken we have to get the type # via the literal. # Sets do not support indexing -> convert to list as simply # popping the only element would modify the argument. sargs = type([])(sargs) sargs = sargs[0] else: sargs = ' '.join(spack.util.string.quote(args)) specs = spack.spec.parse(sargs) for spec in specs: if concretize: spec.concretize(tests=tests) # implies normalize elif normalize: spec.normalize(tests=tests) return specs except spack.spec.SpecParseError as e: msg = e.message + "\n" + str(e.string) + "\n" msg += (e.pos + 2) * " " + "^" raise SpackError(msg) except spack.spec.SpecError as e: msg = e.message if e.long_message: msg += e.long_message raise SpackError(msg)
def test_recursive_upstream_dbs(tmpdir_factory, test_store, gen_mock_layout): roots = [str(tmpdir_factory.mktemp(x)) for x in ['a', 'b', 'c']] layouts = [gen_mock_layout(x) for x in ['/ra/', '/rb/', '/rc/']] default = ('build', 'link') z = MockPackage('z', [], []) y = MockPackage('y', [z], [default]) x = MockPackage('x', [y], [default]) mock_repo = MockPackageMultiRepo([x, y, z]) with spack.repo.swap(mock_repo): spec = spack.spec.Spec('x') spec.concretize() db_c = spack.database.Database(roots[2]) db_c.add(spec['z'], layouts[2]) db_b = spack.database.Database(roots[1], upstream_dbs=[db_c]) db_b.add(spec['y'], layouts[1]) db_a = spack.database.Database(roots[0], upstream_dbs=[db_b, db_c]) db_a.add(spec['x'], layouts[0]) upstream_dbs_from_scratch = ( spack.store._construct_upstream_dbs_from_install_roots( [roots[1], roots[2]], _test=True)) db_a_from_scratch = spack.database.Database( roots[0], upstream_dbs=upstream_dbs_from_scratch) assert db_a_from_scratch.db_for_spec_hash( spec.dag_hash()) == (db_a_from_scratch) assert db_a_from_scratch.db_for_spec_hash( spec['y'].dag_hash()) == (upstream_dbs_from_scratch[0]) assert db_a_from_scratch.db_for_spec_hash( spec['z'].dag_hash()) == (upstream_dbs_from_scratch[1]) db_a_from_scratch._check_ref_counts() upstream_dbs_from_scratch[0]._check_ref_counts() upstream_dbs_from_scratch[1]._check_ref_counts() assert (db_a_from_scratch.installed_relatives(spec) == set( spec.traverse(root=False))) assert (db_a_from_scratch.installed_relatives( spec['z'], direction='parents') == set([spec, spec['y']]))
def test_setup_environment(self, modulefile_content, module_configuration): """Tests the internal set-up of run-time environment.""" module_configuration('suffix') content = modulefile_content('mpileaks') assert len([x for x in content if 'setenv FOOBAR' in x]) == 1 assert len( [x for x in content if 'setenv FOOBAR "mpileaks"' in x] ) == 1 spec = spack.spec.Spec('mpileaks') spec.concretize() content = modulefile_content(str(spec['callpath'])) assert len([x for x in content if 'setenv FOOBAR' in x]) == 1 assert len( [x for x in content if 'setenv FOOBAR "callpath"' in x] ) == 1
def test_dev_build_before_until(tmpdir, mock_packages, install_mockery): spec = spack.spec.Spec('[email protected] dev_path=%s' % tmpdir) spec.concretize() with tmpdir.as_cwd(): with open(spec.package.filename, 'w') as f: f.write(spec.package.original_string) with pytest.raises(SystemExit): dev_build('-u', 'edit', '-b', 'edit', '[email protected]') with pytest.raises(SpackCommandError): dev_build('-u', 'phase_that_does_not_exist', '[email protected]') with pytest.raises(SpackCommandError): dev_build('-b', 'phase_that_does_not_exist', '[email protected]')
def parse_specs(args, **kwargs): """Convenience function for parsing arguments from specs. Handles common exceptions and dies if there are errors. """ concretize = kwargs.get('concretize', False) normalize = kwargs.get('normalize', False) tests = kwargs.get('tests', False) sargs = args if not isinstance(args, six.string_types): sargs = ' '.join(spack.util.string.quote(args)) specs = spack.spec.parse(sargs) for spec in specs: if concretize: spec.concretize(tests=tests) # implies normalize elif normalize: spec.normalize(tests=tests) return specs
def create_single_tarball(spec, outdir, force, relative, unsigned, allow_root, signkey, rebuild_index, catch_exceptions): if isinstance(spec, dict): spec = spack.spec.Spec.from_dict(spec) spec.concretize() tty.msg('creating binary cache file for package %s ' % spec.format()) # use return value dictionary in order to allow for additional return # values in the future retval = {"error": None} try: bindist.build_tarball(spec, outdir, force, relative, unsigned, allow_root, signkey, rebuild_index) except spack.error.SpackError as e: if catch_exceptions: retval["error"] = "Spec %s: %s" % (spec.format(), str(e)) else: # if we are not multiproccessing we can re-raise the exception raise e return retval
def test_dev_build_before_until(tmpdir, mock_packages, install_mockery): spec = spack.spec.Spec('[email protected] dev_path=%s' % tmpdir) spec.concretize() with tmpdir.as_cwd(): with open(spec.package.filename, 'w') as f: f.write(spec.package.original_string) with pytest.raises(SystemExit): dev_build('-u', 'edit', '-b', 'edit', '[email protected]') bad_phase = 'phase_that_does_not_exist' not_allowed = 'is not a valid phase' out = dev_build('-u', bad_phase, '[email protected]') assert bad_phase in out assert not_allowed in out out = dev_build('-b', bad_phase, '[email protected]') assert bad_phase in out assert not_allowed in out
def _concrete_spec_from_args(args): spec_str, specfile_path = args.spec, args.spec_file if not spec_str and not specfile_path: tty.error( 'must provide either spec string or path to YAML or JSON specfile') sys.exit(1) if spec_str: try: constraints = spack.cmd.parse_specs(spec_str) spec = spack.store.find(constraints)[0] spec.concretize() except SpecError as spec_error: tty.error('Unable to concretize spec {0}'.format(spec_str)) tty.debug(spec_error) sys.exit(1) return spec return Spec.from_specfile(specfile_path)
def test_build_task_strings(install_mockery): """Tests of build_task repr and str for coverage purposes.""" # Using a package with one dependency spec = spack.spec.Spec('dependent-install') spec.concretize() assert spec.concrete # Ensure key properties match expectations task = inst.BuildTask(spec.package, False, 0, 0, inst.STATUS_ADDED, []) # Cover __repr__ irep = task.__repr__() assert irep.startswith(task.__class__.__name__) assert "status='queued'" in irep # == STATUS_ADDED assert "sequence=" in irep # Cover __str__ istr = str(task) assert "status=queued" in istr # == STATUS_ADDED assert "#dependencies=1" in istr assert "priority=" in istr
def parse_specs(args, **kwargs): """Convenience function for parsing arguments from specs. Handles common exceptions and dies if there are errors. """ concretize = kwargs.get('concretize', False) normalize = kwargs.get('normalize', False) if isinstance(args, (python_list, tuple)): args = " ".join(args) try: specs = spack.spec.parse(args) for spec in specs: if concretize: spec.concretize() # implies normalize elif normalize: spec.normalize() return specs except spack.parse.ParseError, e: tty.error(e.message, e.string, e.pos * " " + "^") sys.exit(1)
def _mock(spec_string): spec = spack.spec.Spec(spec_string) spec.concretize() return writer_cls(spec), spec