| // 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 com.google.devtools.build.lib.packages.util.TargetDataSubject.assertThat; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.lib.cmdline.PackageIdentifier; |
| import com.google.devtools.build.lib.cmdline.RepositoryName; |
| import com.google.devtools.build.lib.packages.PackageSpecification.PackageGroupContents; |
| import com.google.devtools.build.lib.packages.util.PackageLoadingTestCase; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Unit tests for PackageGroup. */ |
| @RunWith(JUnit4.class) |
| public class PackageGroupTest extends PackageLoadingTestCase { |
| |
| @Test |
| public void testDoesNotFailHorribly() throws Exception { |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['//random'],", |
| ")"); |
| // Note that, for our purposes, the packages listed in the package_group need not exist. |
| |
| getPackageGroup("fruits", "apple"); |
| } |
| |
| // Regression test for: "Package group with empty name causes Blaze exception" |
| @Test |
| public void testEmptyPackageGroupNameDoesNotThrow() throws Exception { |
| scratch.file( |
| "strawberry/BUILD", // |
| "package_group(", |
| " name = '',", |
| " packages=[],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| // Call getTarget() directly since getPackageGroup() requires a name. |
| getTarget("//strawberry:BUILD"); |
| assertContainsEvent("package group has invalid name"); |
| } |
| |
| @Test |
| public void testAbsolutePackagesWork() throws Exception { |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['//vegetables'],", |
| ")"); |
| |
| PackageGroup grp = getPackageGroup("fruits", "apple"); |
| assertThat(grp.contains(pkgId("vegetables"))).isTrue(); |
| assertThat(grp.contains(pkgId("fruits/vegetables"))).isFalse(); |
| } |
| |
| @Test |
| public void testPackagesWithoutDoubleSlashDoNotWork() throws Exception { |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['vegetables'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "apple"); |
| assertContainsEvent("invalid package name 'vegetables'"); |
| } |
| |
| @Test |
| public void testPackagesWithRepositoryDoNotWork() throws Exception { |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'banana',", |
| " packages = ['@veggies//:cucumber'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "banana"); |
| assertContainsEvent("invalid package name '@veggies//:cucumber'"); |
| } |
| |
| @Test |
| public void testAllPackagesInMainRepositoryDoesNotWork() throws Exception { |
| scratch.file( |
| "fruits/BUILD", // |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['@//...'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "apple"); |
| assertContainsEvent("invalid package name '@//...'"); |
| } |
| |
| // TODO(brandjon): It'd be nice to include a test here that you can cross repositories via |
| // `includes`: if package_group //:A includes package_group @repo//:B that has "//foo" in its |
| // `packages`, then //:A admits package @repo//foo. Unfortunately PackageLoadingTestCase doesn't |
| // support resolving repos, but similar functionality is tested in |
| // BzlLoadFunctionTest#testBzlVisibility_enumeratedPackagesMultipleRepos. |
| |
| @Test |
| public void testTargetNameAsPackageDoesNotWork1() throws Exception { |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['//vegetables:carrot'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "apple"); |
| assertContainsEvent("invalid package name '//vegetables:carrot'"); |
| } |
| |
| @Test |
| public void testTargetNameAsPackageDoesNotWork2() throws Exception { |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = [':carrot'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "apple"); |
| assertContainsEvent("invalid package name ':carrot'"); |
| } |
| |
| @Test |
| public void testAllBeneathSpecificationWorks() throws Exception { |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'maracuja',", |
| " packages = ['//tropics/...'],", |
| ")"); |
| |
| getPackageGroup("fruits", "maracuja"); |
| } |
| |
| @Test |
| public void testNegative() throws Exception { |
| scratch.file( |
| "test/BUILD", |
| "package_group(", |
| " name = 'packages',", |
| " packages = [", |
| " '//one',", |
| " '//two',", |
| " '-//three',", |
| " '-//four',", |
| " ],", |
| ")"); |
| |
| PackageGroup grp = getPackageGroup("test", "packages"); |
| assertThat(grp.contains(pkgId("one"))).isTrue(); |
| assertThat(grp.contains(pkgId("two"))).isTrue(); |
| assertThat(grp.contains(pkgId("three"))).isFalse(); |
| assertThat(grp.contains(pkgId("four"))).isFalse(); |
| } |
| |
| @Test |
| public void testNegative_noSubpackages() throws Exception { |
| scratch.file( |
| "test/BUILD", |
| "package_group(", |
| " name = 'packages',", |
| " packages = [", |
| " '//pkg/...',", |
| " '-//pkg/one',", |
| " ],", |
| ")"); |
| |
| PackageGroup grp = getPackageGroup("test", "packages"); |
| assertThat(grp.contains(pkgId("pkg"))).isTrue(); |
| assertThat(grp.contains(pkgId("pkg/one"))).isFalse(); |
| assertThat(grp.contains(pkgId("pkg/one/two"))).isTrue(); |
| } |
| |
| @Test |
| public void testNegative_subpackages() throws Exception { |
| scratch.file( |
| "test/BUILD", |
| "package_group(", |
| " name = 'packages',", |
| " packages = [", |
| " '//pkg/...',", |
| " '-//pkg/one/...',", |
| " ],", |
| ")"); |
| |
| PackageGroup grp = getPackageGroup("test", "packages"); |
| assertThat(grp.contains(pkgId("pkg"))).isTrue(); |
| assertThat(grp.contains(pkgId("pkg/one"))).isFalse(); |
| assertThat(grp.contains(pkgId("pkg/one/two"))).isFalse(); |
| } |
| |
| @Test |
| public void testEverythingSpecificationWorks() throws Exception { |
| setBuildLanguageOptions("--incompatible_package_group_has_public_syntax=true"); |
| |
| scratch.file( |
| "fruits/BUILD", // |
| "package_group(", |
| " name = 'mango',", |
| " packages = ['public'],", |
| ")"); |
| PackageGroup grp = getPackageGroup("fruits", "mango"); |
| |
| // Assert that we're using the right package spec. |
| assertThat(grp.getContainedPackages(/*includeDoubleSlash=*/ true)).containsExactly("public"); |
| // Assert that this package spec contains packages from both inside and outside the main repo. |
| assertThat(grp.contains(pkgId("pkg"))).isTrue(); |
| assertThat(grp.contains(pkgId("somerepo", "pkg"))).isTrue(); |
| } |
| |
| @Test |
| public void testNothingSpecificationWorks() throws Exception { |
| setBuildLanguageOptions("--incompatible_package_group_has_public_syntax=true"); |
| |
| scratch.file( |
| "fruits/BUILD", // |
| "package_group(", |
| " name = 'mango',", |
| " packages = ['private'],", |
| ")"); |
| PackageGroup grp = getPackageGroup("fruits", "mango"); |
| |
| // Assert that we're using the right package spec. |
| assertThat(grp.getContainedPackages(/*includeDoubleSlash=*/ true)).containsExactly("private"); |
| assertThat(grp.contains(pkgId("anything"))).isFalse(); |
| } |
| |
| @Test |
| public void testPublicPrivateAreNotAccessibleWithoutFlag() throws Exception { |
| setBuildLanguageOptions( |
| // Flag being tested |
| "--incompatible_package_group_has_public_syntax=false", |
| // Must also be disabled in order to disable the above |
| "--incompatible_fix_package_group_reporoot_syntax=false"); |
| |
| scratch.file( |
| "foo/BUILD", // |
| "package_group(", |
| " name = 'grp1',", |
| " packages = ['public'],", |
| ")"); |
| scratch.file( |
| "bar/BUILD", // |
| "package_group(", |
| " name = 'grp2',", |
| " packages = ['private'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("foo", "grp1"); |
| assertContainsEvent( |
| "Use of \"public\" package specification requires enabling" |
| + " --incompatible_package_group_has_public_syntax"); |
| getPackageGroup("bar", "grp2"); |
| assertContainsEvent( |
| "Use of \"private\" package specification requires enabling" |
| + " --incompatible_package_group_has_public_syntax"); |
| } |
| |
| @Test |
| public void testRepoRootSubpackagesIsPublic_withoutFlag() throws Exception { |
| setBuildLanguageOptions("--incompatible_fix_package_group_reporoot_syntax=false"); |
| |
| scratch.file( |
| "fruits/BUILD", // |
| "package_group(", |
| " name = 'mango',", |
| " packages = ['//...'],", |
| ")"); |
| PackageGroup grp = getPackageGroup("fruits", "mango"); |
| |
| // Use includeDoubleSlash=true to make package spec stringification distinguish AllPackages from |
| // AllPackagesBeneath with empty package path. |
| assertThat(grp.getContainedPackages(/*includeDoubleSlash=*/ true)) |
| // Assert that "//..." gave us AllPackages. |
| .containsExactly("public"); |
| assertThat(grp.contains(pkgId("pkg"))).isTrue(); |
| assertThat(grp.contains(pkgId("somerepo", "pkg"))).isTrue(); |
| } |
| |
| @Test |
| public void testRepoRootSubpackagesIsNotPublic_withFlag() throws Exception { |
| setBuildLanguageOptions( |
| "--incompatible_package_group_has_public_syntax=true", |
| "--incompatible_fix_package_group_reporoot_syntax=true"); |
| |
| scratch.file( |
| "fruits/BUILD", // |
| "package_group(", |
| " name = 'mango',", |
| " packages = ['//...'],", |
| ")"); |
| PackageGroup grp = getPackageGroup("fruits", "mango"); |
| |
| // Use includeDoubleSlash=true to make package spec stringification distinguish AllPackages from |
| // AllPackagesBeneath with empty package path. |
| assertThat(grp.getContainedPackages(/*includeDoubleSlash=*/ true)) |
| // Assert that "//..." gave us AllPackagesBeneath. |
| .containsExactly("//..."); |
| assertThat(grp.contains(pkgId("pkg"))).isTrue(); |
| assertThat(grp.contains(pkgId("somerepo", "pkg"))).isFalse(); |
| } |
| |
| @Test |
| public void testCannotUseNewRepoRootSyntaxWithoutPublicSyntax() throws Exception { |
| setBuildLanguageOptions( |
| "--incompatible_package_group_has_public_syntax=false", |
| "--incompatible_fix_package_group_reporoot_syntax=true"); |
| |
| scratch.file( |
| "fruits/BUILD", // |
| "package_group(", |
| " name = 'mango',", |
| " packages = ['//something'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "mango"); |
| assertContainsEvent("Cannot use new \"//...\" meaning without allowing new \"public\" syntax."); |
| } |
| |
| @Test |
| public void testNegative_repoRootSubpackages() throws Exception { |
| scratch.file( |
| "test/BUILD", |
| "package_group(", |
| " name = 'packages',", |
| " packages = [", |
| " '//pkg/one',", |
| " '-//...',", |
| " ],", |
| ")"); |
| |
| PackageGroup grp = getPackageGroup("test", "packages"); |
| assertThat(grp.contains(pkgId("pkg"))).isFalse(); |
| assertThat(grp.contains(pkgId("pkg/one"))).isFalse(); |
| assertThat(grp.contains(pkgId("pkg/one/two"))).isFalse(); |
| } |
| |
| @Test |
| public void testNegative_public() throws Exception { |
| setBuildLanguageOptions("--incompatible_package_group_has_public_syntax=true"); |
| |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['-public'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "apple"); |
| assertContainsEvent("Cannot negate \"public\" package specification"); |
| } |
| |
| @Test |
| public void testNegative_private() throws Exception { |
| setBuildLanguageOptions("--incompatible_package_group_has_public_syntax=true"); |
| |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['-private'],", |
| ")"); |
| |
| reporter.removeHandler(failFastHandler); |
| getPackageGroup("fruits", "apple"); |
| assertContainsEvent("Cannot negate \"private\" package specification"); |
| } |
| |
| @Test |
| public void testDuplicatePackage() throws Exception { |
| scratch.file( |
| "test/BUILD", |
| "package_group(", |
| " name = 'packages',", |
| " packages = [", |
| " '//one/two',", |
| " '//one/two',", |
| " ],", |
| ")"); |
| |
| PackageGroup grp = getPackageGroup("test", "packages"); |
| assertThat(grp.contains(pkgId("one/two"))).isTrue(); |
| } |
| |
| @Test |
| public void testStringification() throws Exception { |
| RepositoryName main = RepositoryName.MAIN; |
| RepositoryName other = RepositoryName.create("other"); |
| PackageGroupContents contents = |
| PackageGroupContents.create( |
| ImmutableList.of( |
| pkgSpec(main, "//a"), |
| pkgSpec(main, "//a/b/..."), |
| pkgSpec(main, "-//c"), |
| pkgSpec(main, "-//c/d/..."), |
| pkgSpec(main, "//..."), |
| pkgSpec(main, "-//..."), |
| pkgSpec(main, "//"), |
| pkgSpec(main, "-//"), |
| pkgSpec(other, "//z"), |
| pkgSpec(other, "//..."), |
| pkgSpec(main, "public"), |
| pkgSpec(main, "private"))); |
| assertThat(contents.packageStrings(/* includeDoubleSlash= */ false)) |
| .containsExactly( |
| "a", |
| "", |
| "@@other//z", |
| "a/b/...", |
| "//...", |
| "@@other//...", |
| "-c", |
| "-", |
| "-c/d/...", |
| "-//...", |
| "//...", // legacy syntax for public |
| "private"); |
| assertThat(contents.packageStrings(/* includeDoubleSlash= */ true)) |
| .containsExactly( |
| "//a", |
| "//a/b/...", |
| "-//c", |
| "-//c/d/...", |
| "//...", |
| "-//...", |
| "//", |
| "-//", |
| "@@other//z", |
| "@@other//...", |
| "public", |
| "private"); |
| assertThat(contents.packageStringsWithDoubleSlashAndWithoutRepository()) |
| .containsExactly( |
| "//a", |
| "//a/b/...", |
| "-//c", |
| "-//c/d/...", |
| "//...", |
| "-//...", |
| "//", |
| "-//", |
| "//z", |
| "//...", |
| "public", |
| "private"); |
| } |
| |
| @Test |
| public void testReduceForSerialization() throws Exception { |
| setBuildLanguageOptions("--incompatible_package_group_has_public_syntax=true"); |
| |
| scratch.file( |
| "fruits/BUILD", |
| "package_group(", |
| " name = 'apple',", |
| " packages = ['//vegetables'],", |
| ")", |
| "package_group(", |
| " name = 'mango',", |
| " packages = ['public'],", |
| ")"); |
| PackageGroup grp = getPackageGroup("fruits", "apple"); |
| assertThat(grp).hasSamePropertiesAs(grp.reduceForSerialization()); |
| |
| grp = getPackageGroup("fruits", "mango"); |
| assertThat(grp).hasSamePropertiesAs(grp.reduceForSerialization()); |
| } |
| |
| /** Convenience method for obtaining a PackageSpecification. */ |
| private PackageSpecification pkgSpec(RepositoryName repository, String spec) throws Exception { |
| return PackageSpecification.fromString( |
| repository, spec, /*allowPublicPrivate=*/ true, /*repoRootMeansCurrentRepo=*/ true); |
| } |
| |
| /** Convenience method for obtaining a PackageIdentifier. */ |
| private PackageIdentifier pkgId(String packageName) throws Exception { |
| return PackageIdentifier.createUnchecked(/*repository=*/ "", packageName); |
| } |
| |
| /** Convenience method for obtaining a PackageIdentifier outside the main repo. */ |
| private PackageIdentifier pkgId(String repoName, String packageName) throws Exception { |
| return PackageIdentifier.createUnchecked(repoName, packageName); |
| } |
| |
| /** Evaluates and returns the requested package_group target. */ |
| private PackageGroup getPackageGroup(String pkg, String name) throws Exception { |
| return (PackageGroup) getTarget("//" + pkg + ":" + name); |
| } |
| } |