diff options
Diffstat (limited to 'lib/spack/spack/test/spec_syntax.py')
-rw-r--r-- | lib/spack/spack/test/spec_syntax.py | 201 |
1 files changed, 196 insertions, 5 deletions
diff --git a/lib/spack/spack/test/spec_syntax.py b/lib/spack/spack/test/spec_syntax.py index 043d9b176f..009cb5c129 100644 --- a/lib/spack/spack/test/spec_syntax.py +++ b/lib/spack/spack/test/spec_syntax.py @@ -1,13 +1,13 @@ ############################################################################## -# Copyright (c) 2013-2016, Lawrence Livermore National Security, LLC. +# Copyright (c) 2013-2017, Lawrence Livermore National Security, LLC. # Produced at the Lawrence Livermore National Laboratory. # # This file is part of Spack. # Created by Todd Gamblin, tgamblin@llnl.gov, All rights reserved. # LLNL-CODE-647188 # -# For details, see https://github.com/llnl/spack -# Please also see the LICENSE file for our notice and the LGPL. +# For details, see https://github.com/spack/spack +# Please also see the NOTICE and LICENSE files for our notice and the LGPL. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License (as @@ -25,9 +25,15 @@ import pytest import shlex +import spack import spack.spec as sp from spack.parse import Token -from spack.spec import * +from spack.spec import Spec, parse, parse_anonymous_spec +from spack.spec import SpecParseError, RedundantSpecError +from spack.spec import AmbiguousHashError, InvalidHashError, NoSuchHashError +from spack.spec import DuplicateArchitectureError, DuplicateVariantError +from spack.spec import DuplicateDependencyError, DuplicateCompilerSpecError + # Sample output for a complex lexing. complex_lex = [Token(sp.ID, 'mvapich_foo'), @@ -122,7 +128,7 @@ class TestSpecSyntax(object): def _check_raises(self, exc_type, items): for item in items: with pytest.raises(exc_type): - self.check_parse(item) + Spec(item) # ======================================================================== # Parse checks @@ -132,6 +138,19 @@ class TestSpecSyntax(object): self.check_parse("mvapich_foo") self.check_parse("_mvapich_foo") + 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:') + + 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') + def test_simple_dependence(self): self.check_parse("openmpi^hwloc") self.check_parse("openmpi^hwloc^libunwind") @@ -218,6 +237,163 @@ class TestSpecSyntax(object): 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]) + + def test_spec_by_hash(self, database): + specs = database.mock.db.query() + assert len(specs) # make sure something's in the DB + + for spec in specs: + self._check_hash_parse(spec) + + def test_dep_spec_by_hash(self, database): + mpileaks_zmpi = database.mock.db.query_one('mpileaks ^zmpi') + zmpi = database.mock.db.query_one('zmpi') + fake = database.mock.db.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 + + def test_multiple_specs_with_hash(self, database): + mpileaks_zmpi = database.mock.db.query_one('mpileaks ^zmpi') + callpath_mpich2 = database.mock.db.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 + + def test_ambiguous_hash(self, database): + x1 = Spec('a') + x1._hash = 'xy' + x1._concrete = True + x2 = Spec('a') + x2._hash = 'xx' + x2._concrete = True + database.mock.db.add(x1, spack.store.layout) + database.mock.db.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']) + + def test_invalid_hash(self, database): + mpileaks_zmpi = database.mock.db.query_one('mpileaks ^zmpi') + zmpi = database.mock.db.query_one('zmpi') + + mpileaks_mpich = database.mock.db.query_one('mpileaks ^mpich') + mpich = database.mock.db.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()]) + + def test_nonexistent_hash(self, database): + """Ensure we get errors for nonexistant hashes.""" + specs = database.mock.db.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]) + + 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.mock.db.query_one('mpileaks ^zmpi') + callpath_zmpi = database.mock.db.query_one('callpath ^zmpi') + dyninst = database.mock.db.query_one('dyninst') + + mpileaks_mpich2 = database.mock.db.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) + def test_duplicate_variant(self): duplicates = [ 'x@1.2+debug+debug', @@ -362,3 +538,18 @@ class TestSpecSyntax(object): "mvapich_foo debug= 4 " "^ _openmpi @1.2 : 1.4 , 1.6 % intel @ 12.1 : 12.6 + debug - qt_4 " "^ stackwalker @ 8.1_1e") + + +@pytest.mark.parametrize('spec,anon_spec,spec_name', [ + ('openmpi languages=go', 'languages=go', 'openmpi'), + ('openmpi @4.6:', '@4.6:', 'openmpi'), + ('openmpi languages=go @4.6:', 'languages=go @4.6:', 'openmpi'), + ('openmpi @4.6: languages=go', '@4.6: languages=go', 'openmpi'), +]) +def test_parse_anonymous_specs(spec, anon_spec, spec_name): + + expected = parse(spec) + spec = parse_anonymous_spec(anon_spec, spec_name) + + assert len(expected) == 1 + assert spec in expected |