// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.packages; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; import com.google.devtools.build.lib.cmdline.Label; import com.google.devtools.build.lib.cmdline.PackageIdentifier; import com.google.devtools.build.lib.events.Reporter; import com.google.devtools.build.lib.packages.util.PackageFactoryApparatus; import com.google.devtools.build.lib.packages.util.PackageFactoryTestBase; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Type; import com.google.devtools.build.lib.testutil.TestUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Unit tests for {@code PackageFactory}. */ @RunWith(JUnit4.class) public class PackageFactoryTest extends PackageFactoryTestBase { @Test public void testCreatePackage() throws Exception { Path buildFile = scratch.file("/pkgname/BUILD", "# empty build file "); Package pkg = packages.createPackage("pkgname", buildFile); assertEquals("pkgname", pkg.getName()); assertThat(Sets.newHashSet(pkg.getTargets(Rule.class))).isEmpty(); } @Test public void testCreatePackageIsolatedFromOuterErrors() throws Exception { ExecutorService e = Executors.newCachedThreadPool(); final Semaphore beforeError = new Semaphore(0); final Semaphore afterError = new Semaphore(0); Reporter reporter = new Reporter(new EventBus()); ParsingTracker parser = new ParsingTracker(beforeError, afterError, reporter); final Logger log = Logger.getLogger(PackageFactory.class.getName()); log.addHandler(parser); Level originalLevel = log.getLevel(); log.setLevel(Level.FINE); e.execute(new ErrorReporter(reporter, beforeError, afterError)); e.execute(parser); // wait for all to finish e.shutdown(); assertTrue(e.awaitTermination(TestUtils.WAIT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS)); log.removeHandler(parser); log.setLevel(originalLevel); assertTrue(parser.hasParsed()); } @Test public void testBadRuleName() throws Exception { events.setFailFast(false); Path buildFile = scratch.file("/badrulename/BUILD", "cc_library(name = 3)"); Package pkg = packages.createPackage("badrulename", buildFile); events.assertContainsError("cc_library 'name' attribute must be a string"); assertTrue(pkg.containsErrors()); } @Test public void testNoRuleName() throws Exception { events.setFailFast(false); Path buildFile = scratch.file("/badrulename/BUILD", "cc_library()"); Package pkg = packages.createPackage("badrulename", buildFile); events.assertContainsError("cc_library rule has no 'name' attribute"); assertTrue(pkg.containsErrors()); } @Test public void testBadPackageName() throws Exception { try { // PathFragment parsing de-double slashes, and normalization of the path fragment removes // up reference (/../), so use triple dot /.../ that will always be a forbidden package name. packages.createPackage("not even a legal/.../label", emptyBuildFile("not even a legal/.../label")); fail(); } catch (NoSuchPackageException e) { assertThat(e.getMessage()) .contains( "no such package 'not even a legal/.../label': " + "illegal package name: 'not even a legal/.../label' "); } } @Test public void testColonInExportsFilesTargetName() throws Exception { events.setFailFast(false); Path path = scratch.file( "/googledata/cafe/BUILD", "exports_files(['houseads/house_ads:ca-aol_parenting_html'])"); Package pkg = packages.createPackage("googledata/cafe", path); events.assertContainsError("target names may not contain ':'"); assertThat(pkg.getTargets(FileTarget.class).toString()) .doesNotContain("houseads/house_ads:ca-aol_parenting_html"); assertTrue(pkg.containsErrors()); } @Test public void testPackageNameWithPROTECTEDIsOk() throws Exception { events.setFailFast(false); // One "PROTECTED": assertTrue(isValidPackageName("foo/PROTECTED/bar")); // Multiple "PROTECTED"s: assertTrue(isValidPackageName("foo/PROTECTED/bar/PROTECTED/wiz")); } @Test public void testDuplicateRuleName() throws Exception { events.setFailFast(false); Path buildFile = scratch.file( "/duplicaterulename/BUILD", "# -*- python -*-", "proto_library(name = 'spell_proto', srcs = ['spell.proto'], cc_api_version = 2)", "cc_library(name = 'spell_proto')"); Package pkg = packages.createPackage("duplicaterulename", buildFile); events.assertContainsError( "cc_library rule 'spell_proto' in package " + "'duplicaterulename' conflicts with existing proto_library rule"); assertTrue(pkg.containsErrors()); } @Test public void testDuplicatedDependencies() throws Exception { events.setFailFast(false); Path buildFile = scratch.file( "/has_dupe/BUILD", "cc_library(name='dep')", "cc_library(name='has_dupe', deps=[':dep', ':dep'])"); Package pkg = packages.createPackage("has_dupe", buildFile); events.assertContainsError( "Label '//has_dupe:dep' is duplicated in the 'deps' " + "attribute of rule 'has_dupe'"); assertTrue(pkg.containsErrors()); assertNotNull(pkg.getRule("has_dupe")); assertNotNull(pkg.getRule("dep")); assertTrue(pkg.getRule("has_dupe").containsErrors()); assertTrue(pkg.getRule("dep").containsErrors()); // because all rules in an // errant package are // themselves errant. } @Test public void testPrefixWithinSameRule1() throws Exception { events.setFailFast(false); Path buildFile = scratch.file( "/fruit/orange/BUILD", "genrule(name='orange', srcs=[], outs=['a', 'a/b'], cmd='')"); packages.createPackage("fruit/orange", buildFile); events.assertContainsError("rule 'orange' has conflicting output files 'a/b' and 'a"); } @Test public void testPrefixWithinSameRule2() throws Exception { events.setFailFast(false); Path buildFile = scratch.file( "/fruit/orange/BUILD", "genrule(name='orange', srcs=[], outs=['a/b', 'a'], cmd='')"); packages.createPackage("fruit/orange", buildFile); events.assertContainsError("rule 'orange' has conflicting output files 'a' and 'a/b"); } @Test public void testPrefixBetweenRules1() throws Exception { events.setFailFast(false); Path buildFile = scratch.file( "/fruit/kiwi/BUILD", "genrule(name='kiwi1', srcs=[], outs=['a'], cmd='')", "genrule(name='kiwi2', srcs=[], outs=['a/b'], cmd='')"); packages.createPackage("fruit/kiwi", buildFile); events.assertContainsError( "output file 'a/b' of rule 'kiwi2' conflicts " + "with output file 'a' of rule 'kiwi1'"); } @Test public void testPrefixBetweenRules2() throws Exception { events.setFailFast(false); Path buildFile = scratch.file( "/fruit/kiwi/BUILD", "genrule(name='kiwi1', srcs=[], outs=['a/b'], cmd='')", "genrule(name='kiwi2', srcs=[], outs=['a'], cmd='')"); packages.createPackage("fruit/kiwi", buildFile); events.assertContainsError( "output file 'a' of rule 'kiwi2' conflicts " + "with output file 'a/b' of rule 'kiwi1'"); } @Test public void testPackageConstant() throws Exception { Path buildFile = scratch.file("/pina/BUILD", "cc_library(name=PACKAGE_NAME + '-colada')"); Package pkg = packages.createPackage("pina", buildFile); events.assertNoWarningsOrErrors(); assertFalse(pkg.containsErrors()); assertNotNull(pkg.getRule("pina-colada")); assertFalse(pkg.getRule("pina-colada").containsErrors()); assertSame(1, Sets.newHashSet(pkg.getTargets(Rule.class)).size()); } @Test public void testPackageNameFunction() throws Exception { Path buildFile = scratch.file("/pina/BUILD", "cc_library(name=package_name() + '-colada')"); Package pkg = packages.createPackage("pina", buildFile); events.assertNoWarningsOrErrors(); assertFalse(pkg.containsErrors()); assertNotNull(pkg.getRule("pina-colada")); assertFalse(pkg.getRule("pina-colada").containsErrors()); assertSame(1, Sets.newHashSet(pkg.getTargets(Rule.class)).size()); } @Test public void testPackageConstantInExternalRepository() throws Exception { Path buildFile = scratch.file( "/external/a/b/BUILD", "genrule(name='c', srcs=[], outs=['ao'], cmd=REPOSITORY_NAME + ' ' + PACKAGE_NAME)"); Package pkg = packages.createPackage( PackageIdentifier.create("@a", PathFragment.create("b")), buildFile, events.reporter()); Rule c = pkg.getRule("c"); assertThat(AggregatingAttributeMapper.of(c).get("cmd", Type.STRING)).isEqualTo("@a b"); } @Test public void testPackageFunctionInExternalRepository() throws Exception { Path buildFile = scratch.file( "/external/a/b/BUILD", "genrule(name='c', srcs=[], outs=['o'], cmd=repository_name() + ' ' + package_name())"); Package pkg = packages.createPackage( PackageIdentifier.create("@a", PathFragment.create("b")), buildFile, events.reporter()); Rule c = pkg.getRule("c"); assertThat(AggregatingAttributeMapper.of(c).get("cmd", Type.STRING)).isEqualTo("@a b"); } @Test public void testMultipleDuplicateRuleName() throws Exception { events.setFailFast(false); Path buildFile = scratch.file( "/multipleduplicaterulename/BUILD", "# -*- python -*-", "proto_library(name = 'spellcheck_proto',", " srcs = ['spellcheck.proto'],", " cc_api_version = 2)", "cc_library(name = 'spellcheck_proto')", "proto_library(name = 'spell_proto',", " srcs = ['spell.proto'],", " cc_api_version = 2)", "cc_library(name = 'spell_proto')"); Package pkg = packages.createPackage("multipleduplicaterulename", buildFile); events.assertContainsError( "cc_library rule 'spellcheck_proto' in package " + "'multipleduplicaterulename' conflicts with existing proto_library rule"); events.assertContainsError( "cc_library rule 'spell_proto' in package " + "'multipleduplicaterulename' conflicts with existing proto_library rule"); assertTrue(pkg.containsErrors()); } @Test public void testBuildFileTargetExists() throws Exception { Path buildFile = scratch.file("/foo/BUILD", ""); Package pkg = packages.createPackage("foo", buildFile); Target target = pkg.getTarget("BUILD"); assertEquals("BUILD", target.getName()); // Test that it's memoized: assertSame(target, pkg.getTarget("BUILD")); } @Test public void testCreationOfInputFiles() throws Exception { Path buildFile = scratch.file( "/foo/BUILD", "exports_files(['Z'])", "cc_library(name='W', deps=['X', 'Y'])", "cc_library(name='X', srcs=['X'])", "cc_library(name='Y')"); Package pkg = packages.createPackage("foo", buildFile); assertFalse(pkg.containsErrors()); // X is a rule with a circular self-dependency. assertSame(Rule.class, pkg.getTarget("X").getClass()); // Y is a rule assertSame(Rule.class, pkg.getTarget("Y").getClass()); // Z is a file assertSame(InputFile.class, pkg.getTarget("Z").getClass()); // A is nothing try { pkg.getTarget("A"); fail(); } catch (NoSuchTargetException e) { assertThat(e) .hasMessage( "no such target '//foo:A': " + "target 'A' not declared in package 'foo' defined by /foo/BUILD"); } // These are the only input files: BUILD, Z Set<String> inputFiles = Sets.newTreeSet(); for (InputFile inputFile : pkg.getTargets(InputFile.class)) { inputFiles.add(inputFile.getName()); } assertEquals(ImmutableList.of("BUILD", "Z"), Lists.newArrayList(inputFiles)); } @Test public void testThirdPartyLicenseError() throws Exception { events.setFailFast(false); Path buildFile = scratch.file("/third_party/foo/BUILD", "# line 1", "cc_library(name='bar')", "# line 3"); Package pkg = packages.createPackage("third_party/foo", buildFile); events.assertContainsError( "third-party rule '//third_party/foo:bar' lacks a license " + "declaration with one of the following types: " + "notice, reciprocal, permissive, restricted, unencumbered, by_exception_only"); assertTrue(pkg.containsErrors()); } @Test public void testThirdPartyLicenseExportsFileError() throws Exception { events.setFailFast(false); Path buildFile = scratch.file("/third_party/foo/BUILD", "exports_files(['bar'])"); Package pkg = packages.createPackage("third_party/foo", buildFile); events.assertContainsError( "third-party file 'bar' lacks a license " + "declaration with one of the following types: " + "notice, reciprocal, permissive, restricted, unencumbered, by_exception_only"); assertTrue(pkg.containsErrors()); } @Test public void testDuplicateRuleIsNotAddedToPackage() throws Exception { events.setFailFast(false); Path path = scratch.file( "/dup/BUILD", "proto_library(name = 'dup_proto',", " srcs = ['dup.proto'],", " cc_api_version = 2)", "", "cc_library(name = 'dup_proto',", " srcs = ['dup.pb.cc', 'dup.pb.h'])"); Package pkg = packages.createPackage("dup", path); events.assertContainsError( "cc_library rule 'dup_proto' in package 'dup' " + "conflicts with existing proto_library rule"); assertTrue(pkg.containsErrors()); Rule dupProto = pkg.getRule("dup_proto"); // Check that the first rule of the given name "wins", and that each of the // "winning" rule's outputs is a member of the package. assertEquals("proto_library", dupProto.getRuleClass()); for (OutputFile out : dupProto.getOutputFiles()) { assertThat(pkg.getTargets(FileTarget.class)).contains(out); } } @Test public void testConflictingRuleDoesNotUpdatePackage() throws Exception { events.setFailFast(false); // In this test, rule2's outputs conflict with rule1, so rule2 is rejected. // However, we must check that neither rule2, nor any of its inputs or // outputs is a member of the package, and that the conflicting output file // "out2" still has rule1 as its getGeneratingRule(). Path path = scratch.file( "/conflict/BUILD", "genrule(name = 'rule1',", " cmd = '',", " srcs = ['in1', 'in2'],", " outs = ['out1', 'out2'])", "genrule(name = 'rule2',", " cmd = '',", " srcs = ['in3', 'in4'],", " outs = ['out3', 'out2'])"); Package pkg = packages.createPackage("conflict", path); events.assertContainsError( "generated file 'out2' in rule 'rule2' " + "conflicts with existing generated file from rule 'rule1'"); assertTrue(pkg.containsErrors()); assertNull(pkg.getRule("rule2")); // Ensure that rule2's "out2" didn't overwrite rule1's: assertSame(pkg.getRule("rule1"), ((OutputFile) pkg.getTarget("out2")).getGeneratingRule()); // None of rule2, its inputs, or its outputs should belong to pkg: List<Target> found = new ArrayList<>(); for (String targetName : ImmutableList.of("rule2", "in3", "in4", "out3")) { try { found.add(pkg.getTarget(targetName)); } catch (NoSuchTargetException e) { /* good! */ } } assertThat(found).isEmpty(); } // Was: Regression test for bug "Rules declared after an error in // a package should be considered 'in error'". // Now: Regression test for bug "Why aren't ERRORS considered // fatal?*" @Test public void testAllRulesInErrantPackageAreInError() throws Exception { events.setFailFast(false); Path path = scratch.file( "/error/BUILD", "genrule(name = 'rule1',", " cmd = ':',", " outs = ['out.1'])", "list = ['bad']", "PopulateList(list)", // undefined => error "genrule(name = 'rule2',", " cmd = ':',", " outs = list)"); Package pkg = packages.createPackage("error", path); events.assertContainsError("name 'PopulateList' is not defined"); assertTrue(pkg.containsErrors()); // rule1 would be fine but is still marked as in error: assertTrue(pkg.getRule("rule1").containsErrors()); // rule2 is considered "in error" because it's after an error. // Indeed, it has the wrong "outs" set because the call to PopulateList // failed. Rule rule2 = pkg.getRule("rule2"); assertTrue(rule2.containsErrors()); assertEquals(Sets.newHashSet(pkg.getTarget("bad")), Sets.newHashSet(rule2.getOutputFiles())); } @Test public void testHelpfulErrorForMissingExportsFiles() throws Exception { Path path = scratch.file("/x/BUILD", "cc_library(name='x', srcs=['x.cc'])"); scratch.file("/x/x.cc"); scratch.file("/x/y.cc"); scratch.file("/x/dir/dummy"); Package pkg = packages.createPackage("x", path); assertNotNull(pkg.getTarget("x.cc")); // existing and mentioned. try { pkg.getTarget("y.cc"); // existing but not mentioned. fail(); } catch (NoSuchTargetException e) { assertThat(e) .hasMessage( "no such target '//x:y.cc': " + "target 'y.cc' not declared in package 'x'; " + "however, a source file of this name exists. " + "(Perhaps add 'exports_files([\"y.cc\"])' to x/BUILD?) " + "defined by /x/BUILD"); } try { pkg.getTarget("z.cc"); // non-existent and unmentioned. fail(); } catch (NoSuchTargetException e) { assertThat(e) .hasMessage( "no such target '//x:z.cc': " + "target 'z.cc' not declared in package 'x' (did you mean 'x.cc'?) " + "defined by /x/BUILD"); } try { pkg.getTarget("dir"); // existing directory but not mentioned. fail(); } catch (NoSuchTargetException e) { assertThat(e) .hasMessage( "no such target '//x:dir': target 'dir' not declared in package 'x'; " + "however, a source directory of this name exists. " + "(Perhaps add 'exports_files([\"dir\"])' to x/BUILD, " + "or define a filegroup?) defined by /x/BUILD"); } } @Test public void testTestSuitesImplicitlyDependOnAllRulesInPackage() throws Exception { Path path = scratch.file( "/x/BUILD", "java_test(name='j')", "test_suite(name='t1')", "test_suite(name='t2', tests=['//foo'])", "test_suite(name='t3', tests=['//foo'])", "cc_test(name='c')"); Package pkg = packages.createPackage("x", path); // Things to note: // - the t1 refers to both :j and :c, even though :c is a forward reference. // - $implicit_tests is empty unless tests=[] assertThat(attributes(pkg.getRule("t1")).get("$implicit_tests", BuildType.LABEL_LIST)) .containsExactlyElementsIn( Sets.newHashSet(Label.parseAbsolute("//x:c"), Label.parseAbsolute("//x:j"))); assertThat(attributes(pkg.getRule("t2")).get("$implicit_tests", BuildType.LABEL_LIST)) .isEmpty(); assertThat(attributes(pkg.getRule("t3")).get("$implicit_tests", BuildType.LABEL_LIST)) .isEmpty(); } @Test public void testGlobDirectoryExclusion() throws Exception { emptyFile("/fruit/data/apple"); emptyFile("/fruit/data/pear"); emptyFile("/fruit/data/berry/black"); emptyFile("/fruit/data/berry/blue"); Path file = scratch.file( "/fruit/BUILD", "cc_library(name = 'yes', srcs = glob(['data/*']))", "cc_library(name = 'no', srcs = glob(['data/*'], exclude_directories=0))"); Package pkg = packages.eval("fruit", file); events.assertNoWarningsOrErrors(); List<Label> yesFiles = attributes(pkg.getRule("yes")).get("srcs", BuildType.LABEL_LIST); List<Label> noFiles = attributes(pkg.getRule("no")).get("srcs", BuildType.LABEL_LIST); assertThat(yesFiles).containsExactly( Label.parseAbsolute("@//fruit:data/apple"), Label.parseAbsolute("@//fruit:data/pear")); assertThat(noFiles).containsExactly( Label.parseAbsolute("@//fruit:data/apple"), Label.parseAbsolute("@//fruit:data/pear"), Label.parseAbsolute("@//fruit:data/berry")); } // TODO(bazel-team): This is really a test for GlobCache. @Test public void testRecursiveGlob() throws Exception { emptyFile("/rg/a.cc"); emptyFile("/rg/foo/bar.cc"); emptyFile("/rg/foo/foo.cc"); emptyFile("/rg/foo/wiz/bam.cc"); emptyFile("/rg/foo/wiz/bum.cc"); emptyFile("/rg/foo/wiz/quid/gav.cc"); Path file = scratch.file( "/rg/BUILD", "cc_library(name = 'ri', srcs = glob(['**/*.cc']))", "cc_library(name = 're', srcs = glob(['*.cc'], exclude=['**/*.c']))"); Package pkg = packages.eval("rg", file); events.assertNoWarningsOrErrors(); assertEvaluates( pkg, ImmutableList.of( "BUILD", "a.cc", "foo", "foo/bar.cc", "foo/foo.cc", "foo/wiz", "foo/wiz/bam.cc", "foo/wiz/bum.cc", "foo/wiz/quid", "foo/wiz/quid/gav.cc"), "**"); assertEvaluates( pkg, ImmutableList.of( "a.cc", "foo/bar.cc", "foo/foo.cc", "foo/wiz/bam.cc", "foo/wiz/bum.cc", "foo/wiz/quid/gav.cc"), "**/*.cc"); assertEvaluates( pkg, ImmutableList.of("foo/bar.cc", "foo/wiz/bam.cc", "foo/wiz/bum.cc"), "**/b*.cc"); assertEvaluates( pkg, ImmutableList.of( "foo/bar.cc", "foo/foo.cc", "foo/wiz/bam.cc", "foo/wiz/bum.cc", "foo/wiz/quid/gav.cc"), "**/*/*.cc"); assertEvaluates(pkg, ImmutableList.of("foo/wiz/quid/gav.cc"), "foo/**/quid/*.cc"); assertEvaluates( pkg, Collections.<String>emptyList(), ImmutableList.of("*.cc", "*/*.cc", "*/*/*.cc"), ImmutableList.of("**/*.cc")); assertEvaluates( pkg, Collections.<String>emptyList(), ImmutableList.of("**/*.cc"), ImmutableList.of("**/*.cc")); assertEvaluates( pkg, Collections.<String>emptyList(), ImmutableList.of("**/*.cc"), ImmutableList.of("*.cc", "*/*.cc", "*/*/*.cc", "*/*/*/*.cc")); assertEvaluates( pkg, Collections.<String>emptyList(), ImmutableList.of("**"), ImmutableList.of("*", "*/*", "*/*/*", "*/*/*/*")); assertEvaluates( pkg, ImmutableList.of( "foo/bar.cc", "foo/foo.cc", "foo/wiz/bam.cc", "foo/wiz/bum.cc", "foo/wiz/quid/gav.cc"), ImmutableList.of("**/*.cc"), ImmutableList.of("*.cc")); assertEvaluates( pkg, ImmutableList.of("a.cc", "foo/wiz/bam.cc", "foo/wiz/bum.cc", "foo/wiz/quid/gav.cc"), ImmutableList.of("**/*.cc"), ImmutableList.of("*/*.cc")); assertEvaluates( pkg, ImmutableList.of("a.cc", "foo/bar.cc", "foo/foo.cc", "foo/wiz/quid/gav.cc"), ImmutableList.of("**/*.cc"), ImmutableList.of("**/wiz/*.cc")); } @Test public void testInsufficientArgumentGlobErrors() throws Exception { events.setFailFast(false); assertGlobFails( "glob()", "insufficient arguments received by glob(include: sequence of strings, " + "*, exclude: sequence of strings = [], exclude_directories: int = 1) " + "(got 0, expected at least 1)"); } @Test public void testGlobUnamedExclude() throws Exception { events.setFailFast(false); assertGlobFails( "glob(['a'], ['b'])", "too many (2) positional arguments in call to glob(include: sequence of strings, " + "*, exclude: sequence of strings = [], exclude_directories: int = 1)"); } @Test public void testTooManyArgumentsGlobErrors() throws Exception { events.setFailFast(false); assertGlobFails( "glob(1,2,3,4)", "too many (4) positional arguments in call to glob(include: sequence of strings, " + "*, exclude: sequence of strings = [], exclude_directories: int = 1)"); } @Test public void testGlobEnforcesListArgument() throws Exception { events.setFailFast(false); assertGlobFails( "glob(1, exclude=2)", "method glob(include: sequence of strings, *, exclude: sequence of strings, " + "exclude_directories: int) is not applicable for arguments (int, int, int): " + "'include' is 'int', but should be 'sequence'"); } @Test public void testGlobEnforcesListOfStringsArguments() throws Exception { events.setFailFast(false); assertGlobFails( "glob(['a', 'b'], exclude=['c', 42])", "expected value of type 'string' for element 1 of 'glob' argument, but got 42 (int)"); } @Test public void testGlobNegativeTest() throws Exception { // Negative test that assertGlob does throw an error when asserting against the wrong values. try { assertGlobMatches( /*result=*/ ImmutableList.of("Wombat1.java", "This_file_doesn_t_exist.java"), /*includes=*/ ImmutableList.of("W*", "subdir"), /*excludes=*/ ImmutableList.<String>of(), /*excludesDirs=*/ true); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("ERROR /globs/BUILD:2:73: name 'this_will_fail' is not defined"); } } @Test public void testGlobExcludeDirectories() throws Exception { assertGlobMatches( /*result=*/ ImmutableList.of("Wombat1.java", "Wombat2.java"), /*includes=*/ ImmutableList.of("W*", "subdir"), /*excludes=*/ ImmutableList.<String>of(), /*excludesDirs=*/ true); } @Test public void testGlobDoesNotExcludeDirectories() throws Exception { assertGlobMatches( /*result=*/ ImmutableList.of("Wombat1.java", "Wombat2.java", "subdir"), /*includes=*/ ImmutableList.of("W*", "subdir"), /*excludes=*/ ImmutableList.<String>of(), /*excludesDirs=*/ false); } @Test public void testGlobWithEmptyExcludedList() throws Exception { assertGlobMatches( /*result=*/ ImmutableList.of("Wombat1.java", "Wombat2.java"), /*includes=*/ ImmutableList.of("W*"), /*excludes=*/ Collections.<String>emptyList(), /*excludesDirs=*/ false); } @Test public void testGlobWithQuestionMarkProducesError() throws Exception { assertGlobProducesError("Wombat?.java", true); } @Test public void testGlobWithoutQuestionMarkDoesntProduceError() throws Exception { assertGlobProducesError("Wombat*.java", false); } @Test public void testGlobWithNonMatchingExcludedList() throws Exception { assertGlobMatches( /*result=*/ ImmutableList.of("Wombat1.java"), /*includes=*/ ImmutableList.of("W*"), /*excludes=*/ ImmutableList.of("*2*"), /*excludesDirs=*/ false); } @Test public void testGlobWithTwoMatchingGlobExpressionsAndNonmatchingExclusion() throws Exception { assertGlobMatches( /*result=*/ ImmutableList.of("Wombat1.java", "subdir/Wombat3.java"), /*includes=*/ ImmutableList.of("W*", "subdir/W*"), /*excludes=*/ ImmutableList.of("*2*"), /*excludesDirs=*/ false); } @Test public void testGlobWithSubdirMatchAndExclusion() throws Exception { assertGlobMatches( /*result=*/ ImmutableList.of("subdir/Wombat3.java"), /*includes=*/ ImmutableList.of("W*", "subdir/W*"), /*excludes=*/ ImmutableList.of("Wombat*.java"), /*excludesDirs=*/ false); } @Test public void testBadCharactersInGlob() throws Exception { events.setFailFast(false); assertGlobFails("glob(['{'])", "illegal character"); assertGlobFails("glob(['?'])", "illegal character"); } /** * Tests that a glob evaluation that encounters an I/O error produces * a glob error. */ @Test public void testGlobWithIOErrors() throws Exception { events.setFailFast(false); scratch.dir("/pkg"); scratch.dir("/pkg/globs"); Path unreadableSubdir = scratch.resolve("/pkg/globs/unreadable_subdir"); unreadableSubdir.createDirectory(); unreadableSubdir.setReadable(false); Path file = scratch.file("/pkg/BUILD", "cc_library(name = 'c', srcs = glob(['globs/**']))"); packages.eval("pkg", file); events.assertContainsError("error globbing [globs/**]: Directory is not readable"); } @Test public void testPackageGroupSpecMinimal() throws Exception { expectEvalSuccess("package_group(name='skin', packages=[])"); } @Test public void testPackageGroupSpecSimple() throws Exception { expectEvalSuccess("package_group(name='skin', packages=['//group/abelian'])"); } @Test public void testPackageGroupSpecEmpty() throws Exception { expectEvalSuccess("package_group(name='seed')"); } @Test public void testPackageGroupSpecIncludes() throws Exception { expectEvalSuccess( "package_group(name='wine',", " includes=['//wine:cabernet_sauvignon',", " '//wine:pinot_noir'])"); } @Test public void testPackageGroupSpecBad() throws Exception { expectEvalError("invalid package name", "package_group(name='skin', packages=['--25:17--'])"); } @Test public void testPackageGroupsWithSameName() throws Exception { expectEvalError( "conflicts with existing package group", "package_group(name='skin', packages=[])", "package_group(name='skin', packages=[])"); } @Test public void testPackageGroupNamedArguments() throws Exception { expectEvalError("does not accept positional arguments", "package_group('skin')"); } @Test public void testPackageSpecMinimal() throws Exception { Package pkg = expectEvalSuccess("package(default_visibility=[])"); assertNotNull(pkg.getDefaultVisibility()); } @Test public void testPackageSpecSimple() throws Exception { expectEvalSuccess("package(default_visibility=['//group:lie'])"); } @Test public void testPackageSpecBad() throws Exception { expectEvalError("invalid target name", "package(default_visibility=[':::'])"); } @Test public void testDoublePackageSpecification() throws Exception { expectEvalError( "can only be used once", "package(default_visibility=[])", "package(default_visibility=[])"); } @Test public void testEmptyPackageSpecification() throws Exception { expectEvalError("at least one argument must be given to the 'package' function", "package()"); } @Test public void testDefaultTestonly() throws Exception { Package pkg = expectEvalSuccess("package(default_testonly = 1)"); assertTrue(pkg.getDefaultTestOnly()); } @Test public void testDefaultDeprecation() throws Exception { String testMessage = "OMG PONIES!"; Package pkg = expectEvalSuccess("package(default_deprecation = \"" + testMessage + "\")"); assertEquals(testMessage, pkg.getDefaultDeprecation()); } @Test public void testExportsBuildFile() throws Exception { Package pkg = expectEvalSuccess("exports_files(['BUILD'], visibility=['//visibility:private'])"); assertEquals(pkg.getBuildFile(), pkg.getTarget("BUILD")); } @Test public void testDefaultDeprecationPropagation() throws Exception { String msg = "I am completely operational, and all my circuits are functioning perfectly."; Path file = scratch.file( "/foo/BUILD", "package(default_deprecation = \"" + msg + "\")", "sh_library(name = 'bar', srcs=['b'])"); Package pkg = packages.eval("foo", file); Rule fooRule = (Rule) pkg.getTarget("bar"); String deprAttr = attributes(fooRule).get("deprecation", com.google.devtools.build.lib.syntax.Type.STRING); assertEquals(msg, deprAttr); } @Test public void testDefaultTestonlyPropagation() throws Exception { Path file = scratch.file( "/foo/BUILD", "package(default_testonly = 1)", "sh_library(name = 'foo', srcs=['b'])", "sh_library(name = 'bar', srcs=['b'], testonly = 0)"); Package pkg = packages.eval("foo", file); Rule fooRule = (Rule) pkg.getTarget("foo"); assertTrue( attributes(fooRule).get("testonly", com.google.devtools.build.lib.syntax.Type.BOOLEAN)); Rule barRule = (Rule) pkg.getTarget("bar"); assertFalse( attributes(barRule).get("testonly", com.google.devtools.build.lib.syntax.Type.BOOLEAN)); } @Test public void testDefaultDeprecationOverriding() throws Exception { String msg = "I am completely operational, and all my circuits are functioning perfectly."; String deceive = "OMG PONIES!"; Path file = scratch.file( "/foo/BUILD", "package(default_deprecation = \"" + deceive + "\")", "sh_library(name = 'bar', srcs=['b'], deprecation = \"" + msg + "\")"); Package pkg = packages.eval("foo", file); Rule fooRule = (Rule) pkg.getTarget("bar"); String deprAttr = attributes(fooRule).get("deprecation", com.google.devtools.build.lib.syntax.Type.STRING); assertEquals(msg, deprAttr); } @Test public void testPackageFeatures() throws Exception { Path file = scratch.file( "/a/BUILD", "sh_library(name='before')", "package(features=['b', 'c'])", "sh_library(name='after')"); Package pkg = packages.eval("a", file); assertThat(pkg.getRule("before").getFeatures()).containsExactly("b", "c"); assertThat(pkg.getRule("after").getFeatures()).containsExactly("b", "c"); assertThat(pkg.getFeatures()).containsExactly("b", "c"); } @Test public void testTransientErrorsInGlobbing() throws Exception { events.setFailFast(false); Path buildFile = scratch.file("/e/BUILD", "sh_library(name = 'e', data = glob(['*.txt']))"); Path parentDir = buildFile.getParentDirectory(); scratch.file("/e/data.txt"); throwOnReaddir = parentDir; Package pkg = packages.createPackage("e", buildFile); assertTrue(pkg.containsErrors()); events.setFailFast(true); throwOnReaddir = null; pkg = packages.createPackage("e", buildFile); assertFalse(pkg.containsErrors()); assertNotNull(pkg.getRule("e")); GlobList globList = (GlobList) pkg.getRule("e").getAttributeContainer().getAttr("data"); assertThat(globList).containsExactly(Label.parseAbsolute("//e:data.txt")); } @Test public void testExportTwicePublicOK() throws Exception { // In theory, this could be an error, but too many existing files rely on it // and it is okay. expectEvalSuccess( "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:public\" ])", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:public\" ])"); } @Test public void testExportTwicePublicOK2() throws Exception { expectEvalSuccess( "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:private\" ])", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:private\" ])"); } @Test public void testExportTwiceFail() throws Exception { expectEvalError( "visibility for exported file 'a.cc' declared twice", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:private\" ])", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:public\" ])"); } @Test public void testExportTwiceFail2() throws Exception { expectEvalError( "visibility for exported file 'a.cc' declared twice", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:public\" ])", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:private\" ])"); } @Test public void testExportLicenseTwice() throws Exception { expectEvalError( "licenses for exported file 'a.cc' declared twice", "exports_files([\"a.cc\"], licenses = [\"notice\"])", "exports_files([\"a.cc\"], licenses = [\"notice\"])"); } @Test public void testExportGenruleConflict() throws Exception { expectEvalError( "generated file 'a.cc' in rule 'foo' conflicts with existing source file", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:public\" ])", "genrule(name = 'foo',", " outs = ['a.cc'],", " cmd = '')"); } @Test public void testGenruleExportConflict() throws Exception { expectEvalError( "generated label '//pkg:a.cc' conflicts with existing generated file", "genrule(name = 'foo',", " outs = ['a.cc'],", " cmd = '')", "exports_files([\"a.cc\"],", " visibility = [ \"//visibility:public\" ])"); } @Test public void testValidEnvironmentGroup() throws Exception { expectEvalSuccess( "environment(name = 'foo')", "environment_group(name='group', environments = [':foo'], defaults = [':foo'])"); } @Test public void testIncompleteEnvironmentGroup() throws Exception { expectEvalError( "missing mandatory named-only argument 'defaults' while calling " + "environment_group(*, name: string, ", "environment(name = 'foo')", "environment_group(name='group', environments = [':foo'])"); } @Test public void testEnvironmentGroupMissingTarget() throws Exception { expectEvalError( "environment //pkg:foo does not exist", "environment_group(name='group', environments = [':foo'], defaults = [':foo'])"); } @Test public void testEnvironmentGroupWrongTargetType() throws Exception { expectEvalError( "//pkg:foo is not a valid environment", "cc_library(name = 'foo')", "environment_group(name='group', environments = [':foo'], defaults = [':foo'])"); } @Test public void testEnvironmentGroupWrongPackage() throws Exception { expectEvalError( "//foo:foo is not in the same package as group //pkg:group", "environment_group(name='group', environments = ['//foo'], defaults = ['//foo'])"); } @Test public void testEnvironmentGroupInvalidDefault() throws Exception { expectEvalError( "default //pkg:bar is not a declared environment for group //pkg:group", "environment(name = 'foo')", "environment(name = 'bar')", "environment_group(name='group', environments = [':foo'], defaults = [':bar'])"); } @Test public void testEnvironmentGroupDuplicateEnvironments() throws Exception { expectEvalError( "label '//pkg:foo' is duplicated in the 'environments' list of 'group'", "environment(name = 'foo')", "environment_group(name='group', environments = [':foo', ':foo'], defaults = [':foo'])"); } @Test public void testEnvironmentGroupDuplicateDefaults() throws Exception { expectEvalError( "label '//pkg:foo' is duplicated in the 'defaults' list of 'group'", "environment(name = 'foo')", "environment_group(name='group', environments = [':foo'], defaults = [':foo', ':foo'])"); } @Test public void testMultipleEnvironmentGroupsValidMembership() throws Exception { expectEvalSuccess( "environment(name = 'foo')", "environment(name = 'bar')", "environment_group(name='foo_group', environments = [':foo'], defaults = [':foo'])", "environment_group(name='bar_group', environments = [':bar'], defaults = [':bar'])"); } @Test public void testMultipleEnvironmentGroupsConflictingMembership() throws Exception { expectEvalError( "environment //pkg:foo belongs to both //pkg:bar_group and //pkg:foo_group", "environment(name = 'foo')", "environment(name = 'bar')", "environment_group(name='foo_group', environments = [':foo'], defaults = [':foo'])", "environment_group(name='bar_group', environments = [':foo'], defaults = [':foo'])"); } @Test public void testFulfillsReferencesWrongTargetType() throws Exception { expectEvalError( "in \"fulfills\" attribute of //pkg:foo: //pkg:bar is not a valid environment", "environment(name = 'foo', fulfills = [':bar'])", "cc_library(name = 'bar')", "environment_group(name='foo_group', environments = [':foo'], defaults = [])"); } @Test public void testFulfillsNotInEnvironmentGroup() throws Exception { expectEvalError( "in \"fulfills\" attribute of //pkg:foo: //pkg:bar is not a member of this group", "environment(name = 'foo', fulfills = [':bar'])", "environment(name = 'bar')", "environment_group(name='foo_group', environments = [':foo'], defaults = [])"); } @Test public void testPackageDefaultEnvironments() throws Exception { Package pkg = expectEvalSuccess( "package(", " default_compatible_with=['//foo'],", " default_restricted_to=['//bar'],", ")"); assertThat(pkg.getDefaultCompatibleWith()).containsExactly(Label.parseAbsolute("//foo")); assertThat(pkg.getDefaultRestrictedTo()).containsExactly(Label.parseAbsolute("//bar")); } @Test public void testPackageDefaultCompatibilityDuplicates() throws Exception { expectEvalError( "'//foo:foo' is duplicated in the 'default_compatible_with' list", "package(default_compatible_with=['//foo', '//bar', '//foo'])"); } @Test public void testPackageDefaultRestrictionDuplicates() throws Exception { expectEvalError( "'//foo:foo' is duplicated in the 'default_restricted_to' list", "package(default_restricted_to=['//foo', '//bar', '//foo'])"); } /** * Test that build files that reassign builtins fail correctly. */ @Test public void testReassignPrimitive() throws Exception { expectEvalError( "Reassignment of builtin build function 'cc_binary' not permitted", "cc_binary = (['hello.cc'])", "cc_binary(name = 'hello',", " srcs=['hello.cc'],", " malloc = '//base:system_malloc')"); } @Override protected PackageFactoryApparatus createPackageFactoryApparatus() { return new PackageFactoryApparatus(events.reporter()); } @Override protected String getPathPrefix() { return ""; } }