Source code for spack.test.spec_syntax

# Copyright 2013-2022 Lawrence Livermore National Security, LLC and other
# Spack Project Developers. See the top-level COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
import itertools
import os
import shlex

import pytest

import llnl.util.filesystem as fs

import spack.hash_types as ht
import spack.repo
import spack.spec as sp
import spack.store
from spack.parse import Token
from spack.spec import (
    AmbiguousHashError,
    DuplicateArchitectureError,
    DuplicateCompilerSpecError,
    DuplicateDependencyError,
    InvalidHashError,
    MultipleVersionError,
    NoSuchHashError,
    NoSuchSpecFileError,
    RedundantSpecError,
    Spec,
    SpecFilenameError,
    SpecParseError,
)
from spack.variant import DuplicateVariantError

# Sample output for a complex lexing.
complex_lex = [Token(sp.ID, 'mvapich_foo'),
               Token(sp.DEP),
               Token(sp.ID, '_openmpi'),
               Token(sp.AT),
               Token(sp.ID, '1.2'),
               Token(sp.COLON),
               Token(sp.ID, '1.4'),
               Token(sp.COMMA),
               Token(sp.ID, '1.6'),
               Token(sp.PCT),
               Token(sp.ID, 'intel'),
               Token(sp.AT),
               Token(sp.ID, '12.1'),
               Token(sp.COLON),
               Token(sp.ID, '12.6'),
               Token(sp.ON),
               Token(sp.ID, 'debug'),
               Token(sp.OFF),
               Token(sp.ID, 'qt_4'),
               Token(sp.DEP),
               Token(sp.ID, 'stackwalker'),
               Token(sp.AT),
               Token(sp.ID, '8.1_1e')]

# Another sample lexer output with a kv pair.
kv_lex = [Token(sp.ID, 'mvapich_foo'),
          Token(sp.ID, 'debug'),
          Token(sp.EQ),
          Token(sp.VAL, '4'),
          Token(sp.DEP),
          Token(sp.ID, '_openmpi'),
          Token(sp.AT),
          Token(sp.ID, '1.2'),
          Token(sp.COLON),
          Token(sp.ID, '1.4'),
          Token(sp.COMMA),
          Token(sp.ID, '1.6'),
          Token(sp.PCT),
          Token(sp.ID, 'intel'),
          Token(sp.AT),
          Token(sp.ID, '12.1'),
          Token(sp.COLON),
          Token(sp.ID, '12.6'),
          Token(sp.ON),
          Token(sp.ID, 'debug'),
          Token(sp.OFF),
          Token(sp.ID, 'qt_4'),
          Token(sp.DEP),
          Token(sp.ID, 'stackwalker'),
          Token(sp.AT),
          Token(sp.ID, '8.1_1e')]


[docs]class TestSpecSyntax(object): # ======================================================================== # Parse checks # ========================================================================
[docs] def check_parse(self, expected, spec=None): """Assert that the provided spec is able to be parsed. If this is called with one argument, it assumes that the string is canonical (i.e., no spaces and ~ instead of - for variants) and that it will convert back to the string it came from. If this is called with two arguments, the first argument is the expected canonical form and the second is a non-canonical input to be parsed. """ if spec is None: spec = expected output = sp.parse(spec) parsed = (" ".join(str(spec) for spec in output)) assert expected == parsed
[docs] def check_lex(self, tokens, spec): """Check that the provided spec parses to the provided token list.""" spec = shlex.split(str(spec)) lex_output = sp.SpecLexer().lex(spec) assert len(tokens) == len(lex_output), "unexpected number of tokens" for tok, spec_tok in zip(tokens, lex_output): if tok.type == sp.ID or tok.type == sp.VAL: assert tok == spec_tok else: # Only check the type for non-identifiers. assert tok.type == spec_tok.type
def _check_raises(self, exc_type, items): for item in items: with pytest.raises(exc_type): Spec(item) # ======================================================================== # Parse checks # ========================================================================
[docs] def test_package_names(self): self.check_parse("mvapich") self.check_parse("mvapich_foo") self.check_parse("_mvapich_foo")
[docs] def test_anonymous_specs(self): self.check_parse("%intel") self.check_parse("@2.7") self.check_parse("^zlib") self.check_parse("+foo") self.check_parse("arch=test-None-None", "platform=test") self.check_parse('@2.7:')
[docs] def test_anonymous_specs_with_multiple_parts(self): # Parse anonymous spec with multiple tokens self.check_parse('@4.2: languages=go', 'languages=go @4.2:') self.check_parse('@4.2: languages=go')
[docs] def test_simple_dependence(self): self.check_parse("openmpi ^hwloc") self.check_parse("openmpi ^hwloc", "openmpi^hwloc") self.check_parse("openmpi ^hwloc ^libunwind") self.check_parse("openmpi ^hwloc ^libunwind", "openmpi^hwloc^libunwind")
[docs] def test_version_after_compiler(self): self.check_parse('foo@2.0%bar@1.0', 'foo %bar@1.0 @2.0')
[docs] def test_dependencies_with_versions(self): self.check_parse("openmpi ^hwloc@1.2e6") self.check_parse("openmpi ^hwloc@1.2e6:") self.check_parse("openmpi ^hwloc@:1.4b7-rc3") self.check_parse("openmpi ^hwloc@1.2e6:1.4b7-rc3")
[docs] def test_multiple_specs(self): self.check_parse("mvapich emacs")
[docs] def test_multiple_specs_after_kv(self): self.check_parse('mvapich cppflags="-O3 -fPIC" emacs') self.check_parse('mvapich cflags="-O3" emacs', 'mvapich cflags=-O3 emacs')
[docs] def test_multiple_specs_long_second(self): self.check_parse('mvapich emacs@1.1.1%intel cflags="-O3"', 'mvapich emacs @1.1.1 %intel cflags=-O3') self.check_parse('mvapich cflags="-O3 -fPIC" emacs ^ncurses%intel') self.check_parse('mvapich cflags="-O3 -fPIC" emacs ^ncurses%intel', 'mvapich cflags="-O3 -fPIC" emacs^ncurses%intel')
[docs] def test_full_specs(self): self.check_parse( "mvapich_foo" " ^_openmpi@1.2:1.4,1.6%intel@12.1+debug~qt_4" " ^stackwalker@8.1_1e") self.check_parse( "mvapich_foo" " ^_openmpi@1.2:1.4,1.6%intel@12.1~qt_4 debug=2" " ^stackwalker@8.1_1e") self.check_parse( 'mvapich_foo' ' ^_openmpi@1.2:1.4,1.6%intel@12.1 cppflags="-O3" +debug~qt_4' ' ^stackwalker@8.1_1e') self.check_parse( "mvapich_foo" " ^_openmpi@1.2:1.4,1.6%intel@12.1~qt_4 debug=2" " ^stackwalker@8.1_1e arch=test-redhat6-x86")
[docs] def test_yaml_specs(self): self.check_parse( "yaml-cpp@0.1.8%intel@12.1" " ^boost@3.1.4") tempspec = r"builtin.yaml-cpp%gcc" self.check_parse( tempspec.strip("builtin."), spec=tempspec) tempspec = r"testrepo.yaml-cpp%gcc" self.check_parse( tempspec.strip("testrepo."), spec=tempspec) tempspec = r"builtin.yaml-cpp@0.1.8%gcc" self.check_parse( tempspec.strip("builtin."), spec=tempspec) tempspec = r"builtin.yaml-cpp@0.1.8%gcc@7.2.0" self.check_parse( tempspec.strip("builtin."), spec=tempspec) tempspec = r"builtin.yaml-cpp@0.1.8%gcc@7.2.0" \ r" ^boost@3.1.4" self.check_parse( tempspec.strip("builtin."), spec=tempspec)
[docs] def test_canonicalize(self): self.check_parse( "mvapich_foo" " ^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug~qt_4" " ^stackwalker@8.1_1e", "mvapich_foo " "^_openmpi@1.6,1.2:1.4%intel@12.1:12.6+debug~qt_4 " "^stackwalker@8.1_1e") self.check_parse( "mvapich_foo" " ^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug~qt_4" " ^stackwalker@8.1_1e", "mvapich_foo " "^stackwalker@8.1_1e " "^_openmpi@1.6,1.2:1.4%intel@12.1:12.6~qt_4+debug") self.check_parse( "x ^y@1,2:3,4%intel@1,2,3,4+a~b+c~d+e~f", "x ^y~f+e~d+c~b+a@4,2:3,1%intel@4,3,2,1") self.check_parse( "x arch=test-redhat6-None" " ^y arch=test-None-core2" " ^z arch=linux-None-None", "x os=fe " "^y target=be " "^z platform=linux") self.check_parse( "x arch=test-debian6-core2" " ^y arch=test-debian6-core2", "x os=default_os target=default_target" " ^y os=default_os target=default_target") self.check_parse("x ^y", "x@: ^y@:")
[docs] def test_parse_errors(self): errors = ['x@@1.2', 'x ^y@@1.2', 'x@1.2::', 'x::'] self._check_raises(SpecParseError, errors)
def _check_hash_parse(self, spec): """Check several ways to specify a spec by hash.""" # full hash self.check_parse(str(spec), '/' + spec.dag_hash()) # partial hash self.check_parse(str(spec), '/ ' + spec.dag_hash()[:5]) # name + hash self.check_parse(str(spec), spec.name + '/' + spec.dag_hash()) # name + version + space + partial hash self.check_parse( str(spec), spec.name + '@' + str(spec.version) + ' /' + spec.dag_hash()[:6])
[docs] @pytest.mark.db def test_spec_by_hash(self, database): specs = database.query() assert len(specs) # make sure something's in the DB for spec in specs: self._check_hash_parse(spec)
[docs] @pytest.mark.db def test_dep_spec_by_hash(self, database): mpileaks_zmpi = database.query_one('mpileaks ^zmpi') zmpi = database.query_one('zmpi') fake = database.query_one('fake') assert 'fake' in mpileaks_zmpi assert 'zmpi' in mpileaks_zmpi mpileaks_hash_fake = sp.Spec('mpileaks ^/' + fake.dag_hash()) assert 'fake' in mpileaks_hash_fake assert mpileaks_hash_fake['fake'] == fake mpileaks_hash_zmpi = sp.Spec( 'mpileaks %' + str(mpileaks_zmpi.compiler) + ' ^ / ' + zmpi.dag_hash()) assert 'zmpi' in mpileaks_hash_zmpi assert mpileaks_hash_zmpi['zmpi'] == zmpi assert mpileaks_hash_zmpi.compiler == mpileaks_zmpi.compiler mpileaks_hash_fake_and_zmpi = sp.Spec( 'mpileaks ^/' + fake.dag_hash()[:4] + '^ / ' + zmpi.dag_hash()[:5]) assert 'zmpi' in mpileaks_hash_fake_and_zmpi assert mpileaks_hash_fake_and_zmpi['zmpi'] == zmpi assert 'fake' in mpileaks_hash_fake_and_zmpi assert mpileaks_hash_fake_and_zmpi['fake'] == fake
[docs] @pytest.mark.db def test_multiple_specs_with_hash(self, database): mpileaks_zmpi = database.query_one('mpileaks ^zmpi') callpath_mpich2 = database.query_one('callpath ^mpich2') # name + hash + separate hash specs = sp.parse('mpileaks /' + mpileaks_zmpi.dag_hash() + '/' + callpath_mpich2.dag_hash()) assert len(specs) == 2 # 2 separate hashes specs = sp.parse('/' + mpileaks_zmpi.dag_hash() + '/' + callpath_mpich2.dag_hash()) assert len(specs) == 2 # 2 separate hashes + name specs = sp.parse('/' + mpileaks_zmpi.dag_hash() + '/' + callpath_mpich2.dag_hash() + ' callpath') assert len(specs) == 3 # hash + 2 names specs = sp.parse('/' + mpileaks_zmpi.dag_hash() + ' callpath' + ' callpath') assert len(specs) == 3 # hash + name + hash specs = sp.parse('/' + mpileaks_zmpi.dag_hash() + ' callpath' + ' / ' + callpath_mpich2.dag_hash()) assert len(specs) == 2
[docs] @pytest.mark.db def test_ambiguous_hash(self, mutable_database): x1 = Spec('a') x1.concretize() x1._hash = 'xyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy' x2 = Spec('a') x2.concretize() x2._hash = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' mutable_database.add(x1, spack.store.layout) mutable_database.add(x2, spack.store.layout) # ambiguity in first hash character self._check_raises(AmbiguousHashError, ['/x']) # ambiguity in first hash character AND spec name self._check_raises(AmbiguousHashError, ['a/x'])
[docs] @pytest.mark.db def test_invalid_hash(self, database): mpileaks_zmpi = database.query_one('mpileaks ^zmpi') zmpi = database.query_one('zmpi') mpileaks_mpich = database.query_one('mpileaks ^mpich') mpich = database.query_one('mpich') # name + incompatible hash self._check_raises(InvalidHashError, [ 'zmpi /' + mpich.dag_hash(), 'mpich /' + zmpi.dag_hash()]) # name + dep + incompatible hash self._check_raises(InvalidHashError, [ 'mpileaks ^mpich /' + mpileaks_zmpi.dag_hash(), 'mpileaks ^zmpi /' + mpileaks_mpich.dag_hash()])
[docs] @pytest.mark.db def test_nonexistent_hash(self, database): """Ensure we get errors for nonexistant hashes.""" specs = database.query() # This hash shouldn't be in the test DB. What are the odds :) no_such_hash = 'aaaaaaaaaaaaaaa' hashes = [s._hash for s in specs] assert no_such_hash not in [h[:len(no_such_hash)] for h in hashes] self._check_raises(NoSuchHashError, [ '/' + no_such_hash, 'mpileaks /' + no_such_hash])
[docs] @pytest.mark.db def test_redundant_spec(self, database): """Check that redundant spec constraints raise errors. TODO (TG): does this need to be an error? Or should concrete specs only raise errors if constraints cause a contradiction? """ mpileaks_zmpi = database.query_one('mpileaks ^zmpi') callpath_zmpi = database.query_one('callpath ^zmpi') dyninst = database.query_one('dyninst') mpileaks_mpich2 = database.query_one('mpileaks ^mpich2') redundant_specs = [ # redudant compiler '/' + mpileaks_zmpi.dag_hash() + '%' + str(mpileaks_zmpi.compiler), # redudant version 'mpileaks/' + mpileaks_mpich2.dag_hash() + '@' + str(mpileaks_mpich2.version), # redundant dependency 'callpath /' + callpath_zmpi.dag_hash() + '^ libelf', # redundant flags '/' + dyninst.dag_hash() + ' cflags="-O3 -fPIC"'] self._check_raises(RedundantSpecError, redundant_specs)
[docs] def test_duplicate_variant(self): duplicates = [ 'x@1.2+debug+debug', 'x ^y@1.2+debug debug=true', 'x ^y@1.2 debug=false debug=true', 'x ^y@1.2 debug=false ~debug' ] self._check_raises(DuplicateVariantError, duplicates)
[docs] def test_multiple_versions(self): multiples = [ 'x@1.2@2.3', 'x@1.2:2.3@1.4', 'x@1.2@2.3:2.4', 'x@1.2@2.3,2.4', 'x@1.2 +foo~bar @2.3', 'x@1.2%y@1.2@2.3:2.4', ] self._check_raises(MultipleVersionError, multiples)
[docs] def test_duplicate_dependency(self): self._check_raises(DuplicateDependencyError, ["x ^y ^y"])
[docs] def test_duplicate_compiler(self): duplicates = [ "x%intel%intel", "x%intel%gcc", "x%gcc%intel", "x ^y%intel%intel", "x ^y%intel%gcc", "x ^y%gcc%intel" ] self._check_raises(DuplicateCompilerSpecError, duplicates)
[docs] def test_duplicate_architecture(self): duplicates = [ "x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64", "x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le", "x arch=linux-rhel7-ppc64le arch=linux-rhel7-x86_64", "y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-x86_64", "y ^x arch=linux-rhel7-x86_64 arch=linux-rhel7-ppc64le" ] self._check_raises(DuplicateArchitectureError, duplicates)
[docs] def test_duplicate_architecture_component(self): duplicates = [ "x os=fe os=fe", "x os=fe os=be", "x target=fe target=fe", "x target=fe target=be", "x platform=test platform=test", "x os=fe platform=test target=fe os=fe", "x target=be platform=test os=be os=fe" ] self._check_raises(DuplicateArchitectureError, duplicates)
[docs] @pytest.mark.usefixtures('config') def test_parse_yaml_simple(self, mock_packages, tmpdir): s = Spec('libdwarf') s.concretize() specfile = tmpdir.join('libdwarf.yaml') with specfile.open('w') as f: f.write(s.to_yaml(hash=ht.dag_hash)) # Check an absolute path to spec.yaml by itself: # "spack spec /path/to/libdwarf.yaml" specs = sp.parse(specfile.strpath) assert len(specs) == 1 # Check absolute path to spec.yaml mixed with a clispec, e.g.: # "spack spec mvapich_foo /path/to/libdwarf.yaml" specs = sp.parse('mvapich_foo {0}'.format(specfile.strpath)) assert len(specs) == 2
[docs] @pytest.mark.usefixtures('config') def test_parse_filename_missing_slash_as_spec(self, mock_packages, tmpdir): """Ensure that libelf.yaml parses as a spec, NOT a file.""" s = Spec('libelf') s.concretize() specfile = tmpdir.join('libelf.yaml') # write the file to the current directory to make sure it exists, # and that we still do not parse the spec as a file. with specfile.open('w') as f: f.write(s.to_yaml(hash=ht.dag_hash)) # Check the spec `libelf.yaml` in the working directory, which # should evaluate to a spec called `yaml` in the `libelf` # namespace, NOT a spec for `libelf`. with tmpdir.as_cwd(): specs = sp.parse("libelf.yaml") assert len(specs) == 1 spec = specs[0] assert spec.name == "yaml" assert spec.namespace == "libelf" assert spec.fullname == "libelf.yaml" # check that if we concretize this spec, we get a good error # message that mentions we might've meant a file. with pytest.raises(spack.repo.UnknownPackageError) as exc_info: spec.concretize() assert exc_info.value.long_message assert ("Did you mean to specify a filename with './libelf.yaml'?" in exc_info.value.long_message) # make sure that only happens when the spec ends in yaml with pytest.raises(spack.repo.UnknownPackageError) as exc_info: Spec('builtin.mock.doesnotexist').concretize() assert ( not exc_info.value.long_message or ( "Did you mean to specify a filename with" not in exc_info.value.long_message ) )
[docs] @pytest.mark.usefixtures('config') def test_parse_yaml_dependency(self, mock_packages, tmpdir): s = Spec('libdwarf') s.concretize() specfile = tmpdir.join('libelf.yaml') with specfile.open('w') as f: f.write(s['libelf'].to_yaml(hash=ht.dag_hash)) # Make sure we can use yaml path as dependency, e.g.: # "spack spec libdwarf ^ /path/to/libelf.yaml" specs = sp.parse('libdwarf ^ {0}'.format(specfile.strpath)) assert len(specs) == 1
[docs] @pytest.mark.usefixtures('config') def test_parse_yaml_relative_paths(self, mock_packages, tmpdir): s = Spec('libdwarf') s.concretize() specfile = tmpdir.join('libdwarf.yaml') with specfile.open('w') as f: f.write(s.to_yaml(hash=ht.dag_hash)) file_name = specfile.basename parent_dir = os.path.basename(specfile.dirname) # Relative path to specfile with fs.working_dir(specfile.dirname): # Test for command like: "spack spec libelf.yaml" # This should parse a single spec, but should not concretize. # See test_parse_filename_missing_slash_as_spec() specs = sp.parse('{0}'.format(file_name)) assert len(specs) == 1 # Make sure this also works: "spack spec ./libelf.yaml" specs = sp.parse('./{0}'.format(file_name)) assert len(specs) == 1 # Should also be accepted: "spack spec ../<cur-dir>/libelf.yaml" specs = sp.parse('../{0}/{1}'.format(parent_dir, file_name)) assert len(specs) == 1 # Should also handle mixed clispecs and relative paths, e.g.: # "spack spec mvapich_foo ../<cur-dir>/libelf.yaml" specs = sp.parse('mvapich_foo ../{0}/{1}'.format( parent_dir, file_name)) assert len(specs) == 2
[docs] @pytest.mark.usefixtures('config') def test_parse_yaml_relative_subdir_path(self, mock_packages, tmpdir): s = Spec('libdwarf') s.concretize() specfile = tmpdir.mkdir('subdir').join('libdwarf.yaml') with specfile.open('w') as f: f.write(s.to_yaml(hash=ht.dag_hash)) file_name = specfile.basename # Relative path to specfile with tmpdir.as_cwd(): assert os.path.exists('subdir/{0}'.format(file_name)) # Test for command like: "spack spec libelf.yaml" specs = sp.parse('subdir/{0}'.format(file_name)) assert len(specs) == 1
[docs] @pytest.mark.usefixtures('config') def test_parse_yaml_dependency_relative_paths(self, mock_packages, tmpdir): s = Spec('libdwarf') s.concretize() specfile = tmpdir.join('libelf.yaml') with specfile.open('w') as f: f.write(s['libelf'].to_yaml(hash=ht.dag_hash)) file_name = specfile.basename parent_dir = os.path.basename(specfile.dirname) # Relative path to specfile with fs.working_dir(specfile.dirname): # Test for command like: "spack spec libelf.yaml" specs = sp.parse('libdwarf^{0}'.format(file_name)) assert len(specs) == 1 # Make sure this also works: "spack spec ./libelf.yaml" specs = sp.parse('libdwarf^./{0}'.format(file_name)) assert len(specs) == 1 # Should also be accepted: "spack spec ../<cur-dir>/libelf.yaml" specs = sp.parse('libdwarf^../{0}/{1}'.format( parent_dir, file_name)) assert len(specs) == 1
[docs] def test_parse_yaml_error_handling(self): self._check_raises(NoSuchSpecFileError, [ # Single spec that looks like a yaml path '/bogus/path/libdwarf.yaml', '../../libdwarf.yaml', './libdwarf.yaml', # Dependency spec that looks like a yaml path 'libdwarf^/bogus/path/libelf.yaml', 'libdwarf ^../../libelf.yaml', 'libdwarf^ ./libelf.yaml', # Multiple specs, one looks like a yaml path 'mvapich_foo /bogus/path/libelf.yaml', 'mvapich_foo ../../libelf.yaml', 'mvapich_foo ./libelf.yaml', ])
[docs] def test_nice_error_for_no_space_after_spec_filename(self): """Ensure that omitted spaces don't give weird errors about hashes.""" self._check_raises(SpecFilenameError, [ '/bogus/path/libdwarf.yamlfoobar', 'libdwarf^/bogus/path/libelf.yamlfoobar ^/path/to/bogus.yaml', ])
[docs] @pytest.mark.usefixtures('config') def test_yaml_spec_not_filename(self, mock_packages, tmpdir): with pytest.raises(spack.repo.UnknownPackageError): Spec('builtin.mock.yaml').concretize() with pytest.raises(spack.repo.UnknownPackageError): Spec('builtin.mock.yamlfoobar').concretize()
[docs] @pytest.mark.usefixtures('config') def test_parse_yaml_variant_error(self, mock_packages, tmpdir): s = Spec('a') s.concretize() specfile = tmpdir.join('a.yaml') with specfile.open('w') as f: f.write(s.to_yaml(hash=ht.dag_hash)) with pytest.raises(RedundantSpecError): # Trying to change a variant on a concrete spec is an error sp.parse('{0} ~bvv'.format(specfile.strpath))
# ======================================================================== # Lex checks # ========================================================================
[docs] def test_ambiguous(self): # This first one is ambiguous because - can be in an identifier AND # indicate disabling an option. with pytest.raises(AssertionError): self.check_lex( complex_lex, "mvapich_foo" "^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug-qt_4" "^stackwalker@8.1_1e" )
# The following lexes are non-ambiguous (add a space before -qt_4) # and should all result in the tokens in complex_lex
[docs] def test_minimal_spaces(self): self.check_lex( complex_lex, "mvapich_foo" "^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug -qt_4" "^stackwalker@8.1_1e") self.check_lex( complex_lex, "mvapich_foo" "^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug~qt_4" "^stackwalker@8.1_1e")
[docs] def test_spaces_between_dependences(self): self.check_lex( complex_lex, "mvapich_foo " "^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug -qt_4 " "^stackwalker @ 8.1_1e") self.check_lex( complex_lex, "mvapich_foo " "^_openmpi@1.2:1.4,1.6%intel@12.1:12.6+debug~qt_4 " "^stackwalker @ 8.1_1e")
[docs] def test_spaces_between_options(self): self.check_lex( complex_lex, "mvapich_foo " "^_openmpi @1.2:1.4,1.6 %intel @12.1:12.6 +debug -qt_4 " "^stackwalker @8.1_1e")
[docs] def test_way_too_many_spaces(self): self.check_lex( complex_lex, "mvapich_foo " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e") self.check_lex( complex_lex, "mvapich_foo " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug ~ qt_4 " "^ stackwalker @ 8.1_1e")
[docs] def test_kv_with_quotes(self): self.check_lex( kv_lex, "mvapich_foo debug='4' " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e") self.check_lex( kv_lex, 'mvapich_foo debug="4" ' "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e") self.check_lex( kv_lex, "mvapich_foo 'debug = 4' " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e")
[docs] def test_kv_without_quotes(self): self.check_lex( kv_lex, "mvapich_foo debug=4 " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e")
[docs] def test_kv_with_spaces(self): self.check_lex( kv_lex, "mvapich_foo debug = 4 " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e") self.check_lex( kv_lex, "mvapich_foo debug =4 " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e") self.check_lex( kv_lex, "mvapich_foo debug= 4 " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e")
[docs] @pytest.mark.parametrize('expected_tokens,spec_string', [ ([Token(sp.ID, 'target'), Token(sp.EQ, '='), Token(sp.VAL, 'broadwell')], 'target=broadwell'), ([Token(sp.ID, 'target'), Token(sp.EQ, '='), Token(sp.VAL, ':broadwell,icelake')], 'target=:broadwell,icelake') ]) def test_target_tokenization(self, expected_tokens, spec_string): self.check_lex(expected_tokens, spec_string)
[docs] @pytest.mark.regression('20310') def test_compare_abstract_specs(self): """Spec comparisons must be valid for abstract specs. Check that the spec cmp_key appropriately handles comparing specs for which some attributes are None in exactly one of two specs""" # Add fields in order they appear in `Spec._cmp_node` constraints = [ None, 'foo', 'foo.foo', 'foo.foo@foo', 'foo.foo@foo+foo', 'foo.foo@foo+foo arch=foo-foo-foo', 'foo.foo@foo+foo arch=foo-foo-foo %foo', 'foo.foo@foo+foo arch=foo-foo-foo %foo cflags=foo', ] specs = [Spec(s) for s in constraints] for a, b in itertools.product(specs, repeat=2): # Check that we can compare without raising an error assert a <= b or b < a