|  | // 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.pkgcache; | 
|  |  | 
|  | import static com.google.common.truth.Truth.assertThat; | 
|  | import static org.junit.Assert.assertThrows; | 
|  | import static org.junit.Assert.fail; | 
|  |  | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.ImmutableMap; | 
|  | import com.google.common.collect.ImmutableSet; | 
|  | import com.google.common.collect.Iterables; | 
|  | import com.google.devtools.build.lib.actions.ActionKeyContext; | 
|  | import com.google.devtools.build.lib.analysis.BlazeDirectories; | 
|  | import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; | 
|  | import com.google.devtools.build.lib.analysis.ServerDirectories; | 
|  | import com.google.devtools.build.lib.analysis.util.AnalysisMock; | 
|  | import com.google.devtools.build.lib.clock.BlazeClock; | 
|  | import com.google.devtools.build.lib.cmdline.Label; | 
|  | import com.google.devtools.build.lib.cmdline.PackageIdentifier; | 
|  | import com.google.devtools.build.lib.packages.BuildFileContainsErrorsException; | 
|  | import com.google.devtools.build.lib.packages.NoSuchPackageException; | 
|  | import com.google.devtools.build.lib.packages.NoSuchTargetException; | 
|  | import com.google.devtools.build.lib.packages.Package; | 
|  | import com.google.devtools.build.lib.packages.PackageFactory; | 
|  | import com.google.devtools.build.lib.packages.Target; | 
|  | import com.google.devtools.build.lib.packages.semantics.BuildLanguageOptions; | 
|  | import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction; | 
|  | import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants; | 
|  | import com.google.devtools.build.lib.skyframe.PrecomputedValue; | 
|  | import com.google.devtools.build.lib.skyframe.SkyframeExecutor; | 
|  | import com.google.devtools.build.lib.testutil.FoundationTestCase; | 
|  | import com.google.devtools.build.lib.testutil.MoreAsserts; | 
|  | import com.google.devtools.build.lib.testutil.SkyframeExecutorTestHelper; | 
|  | import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; | 
|  | import com.google.devtools.build.lib.vfs.ModifiedFileSet; | 
|  | import com.google.devtools.build.lib.vfs.Path; | 
|  | import com.google.devtools.build.lib.vfs.Root; | 
|  | import com.google.devtools.common.options.OptionsParser; | 
|  | import java.io.IOException; | 
|  | import java.util.Optional; | 
|  | import java.util.UUID; | 
|  | import net.starlark.java.syntax.StarlarkFile; | 
|  | import org.junit.Before; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.JUnit4; | 
|  |  | 
|  | /** Tests for package loading. */ | 
|  | @RunWith(JUnit4.class) | 
|  | public class PackageLoadingTest extends FoundationTestCase { | 
|  |  | 
|  | private SkyframeExecutor skyframeExecutor; | 
|  | private final ActionKeyContext actionKeyContext = new ActionKeyContext(); | 
|  |  | 
|  | @Before | 
|  | public final void initializeSkyframeExecutor() throws Exception { | 
|  | initializeSkyframeExecutor(/*doPackageLoadingChecks=*/ true); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @param doPackageLoadingChecks when true, a PackageLoader will be called after each package load | 
|  | *     this test performs, and the results compared to SkyFrame's result. | 
|  | */ | 
|  | private void initializeSkyframeExecutor(boolean doPackageLoadingChecks) throws Exception { | 
|  | AnalysisMock analysisMock = AnalysisMock.get(); | 
|  | ConfiguredRuleClassProvider ruleClassProvider = analysisMock.createRuleClassProvider(); | 
|  | BlazeDirectories directories = | 
|  | new BlazeDirectories( | 
|  | new ServerDirectories(outputBase, outputBase, outputBase), | 
|  | rootDirectory, | 
|  | /* defaultSystemJavabase= */ null, | 
|  | analysisMock.getProductName()); | 
|  | PackageFactory.BuilderForTesting packageFactoryBuilder = | 
|  | analysisMock.getPackageFactoryBuilderForTesting(directories); | 
|  | if (!doPackageLoadingChecks) { | 
|  | packageFactoryBuilder.disableChecks(); | 
|  | } | 
|  | skyframeExecutor = | 
|  | BazelSkyframeExecutorConstants.newBazelSkyframeExecutorBuilder() | 
|  | .setPkgFactory(packageFactoryBuilder.build(ruleClassProvider, fileSystem)) | 
|  | .setFileSystem(fileSystem) | 
|  | .setDirectories(directories) | 
|  | .setActionKeyContext(actionKeyContext) | 
|  | .setExtraSkyFunctions(analysisMock.getSkyFunctions(directories)) | 
|  | .build(); | 
|  | SkyframeExecutorTestHelper.process(skyframeExecutor); | 
|  | setUpSkyframe(parsePackageOptions(), parseBuildLanguageOptions()); | 
|  | } | 
|  |  | 
|  | private void setUpSkyframe( | 
|  | PackageOptions packageOptions, BuildLanguageOptions buildLanguageOptions) { | 
|  | PathPackageLocator pkgLocator = | 
|  | PathPackageLocator.create( | 
|  | /*outputBase=*/ null, | 
|  | packageOptions.packagePath, | 
|  | reporter, | 
|  | rootDirectory.asFragment(), | 
|  | rootDirectory, | 
|  | BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY); | 
|  | packageOptions.showLoadingProgress = true; | 
|  | packageOptions.globbingThreads = 7; | 
|  | skyframeExecutor.injectExtraPrecomputedValues( | 
|  | ImmutableList.of( | 
|  | PrecomputedValue.injected( | 
|  | RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE, Optional.empty()))); | 
|  | skyframeExecutor.preparePackageLoading( | 
|  | pkgLocator, | 
|  | packageOptions, | 
|  | buildLanguageOptions, | 
|  | UUID.randomUUID(), | 
|  | ImmutableMap.of(), | 
|  | new TimestampGranularityMonitor(BlazeClock.instance())); | 
|  | skyframeExecutor.setActionEnv(ImmutableMap.of()); | 
|  | skyframeExecutor.setDeletedPackages(ImmutableSet.copyOf(packageOptions.getDeletedPackages())); | 
|  | } | 
|  |  | 
|  | private static OptionsParser parse(String... options) throws Exception { | 
|  | OptionsParser parser = | 
|  | OptionsParser.builder() | 
|  | .optionsClasses(PackageOptions.class, BuildLanguageOptions.class) | 
|  | .build(); | 
|  | parser.parse("--default_visibility=public"); | 
|  | parser.parse(options); | 
|  |  | 
|  | return parser; | 
|  | } | 
|  |  | 
|  | private static PackageOptions parsePackageOptions(String... options) throws Exception { | 
|  | return parse(options).getOptions(PackageOptions.class); | 
|  | } | 
|  |  | 
|  | private static BuildLanguageOptions parseBuildLanguageOptions(String... options) | 
|  | throws Exception { | 
|  | return parse(options).getOptions(BuildLanguageOptions.class); | 
|  | } | 
|  |  | 
|  | protected void setOptions(String... options) throws Exception { | 
|  | setUpSkyframe(parsePackageOptions(options), parseBuildLanguageOptions(options)); | 
|  | } | 
|  |  | 
|  | private PackageManager getPackageManager() { | 
|  | return skyframeExecutor.getPackageManager(); | 
|  | } | 
|  |  | 
|  | private void invalidatePackages() throws InterruptedException { | 
|  | skyframeExecutor.invalidateFilesUnderPathForTesting( | 
|  | reporter, ModifiedFileSet.EVERYTHING_MODIFIED, Root.fromPath(rootDirectory)); | 
|  | } | 
|  |  | 
|  | private Package getPackage(String packageName) | 
|  | throws NoSuchPackageException, InterruptedException { | 
|  | return getPackageManager() | 
|  | .getPackage(reporter, PackageIdentifier.createInMainRepo(packageName)); | 
|  | } | 
|  |  | 
|  | private Target getTarget(Label label) | 
|  | throws NoSuchPackageException, NoSuchTargetException, InterruptedException { | 
|  | return getPackageManager().getTarget(reporter, label); | 
|  | } | 
|  |  | 
|  | private Target getTarget(String label) throws Exception { | 
|  | return getTarget(Label.parseAbsolute(label, ImmutableMap.of())); | 
|  | } | 
|  |  | 
|  | private void createPkg1() throws IOException { | 
|  | scratch.file("pkg1/BUILD", "cc_library(name = 'foo') # a BUILD file"); | 
|  | } | 
|  |  | 
|  | // Check that a substring is present in an error message. | 
|  | private void checkGetPackageFails(String packageName, String expectedMessage) { | 
|  | NoSuchPackageException e = | 
|  | assertThrows(NoSuchPackageException.class, () -> getPackage(packageName)); | 
|  | assertThat(e).hasMessageThat().contains(expectedMessage); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testGetPackage() throws Exception { | 
|  | createPkg1(); | 
|  | Package pkg1 = getPackage("pkg1"); | 
|  | assertThat(pkg1.getName()).isEqualTo("pkg1"); | 
|  | assertThat(pkg1.getFilename().asPath().getPathString()).isEqualTo("/workspace/pkg1/BUILD"); | 
|  | assertThat(getPackageManager().getPackage(reporter, PackageIdentifier.createInMainRepo("pkg1"))) | 
|  | .isSameInstanceAs(pkg1); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testASTIsNotRetained() throws Exception { | 
|  | createPkg1(); | 
|  | Package pkg1 = getPackage("pkg1"); | 
|  | MoreAsserts.assertInstanceOfNotReachable(pkg1, StarlarkFile.class); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testGetNonexistentPackage() { | 
|  | checkGetPackageFails("not-there", "no such package 'not-there': " + "BUILD file not found"); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testGetPackageWithInvalidName() throws Exception { | 
|  | scratch.file("invalidpackagename:42/BUILD", "cc_library(name = 'foo') # a BUILD file"); | 
|  | checkGetPackageFails( | 
|  | "invalidpackagename:42", | 
|  | "no such package 'invalidpackagename:42': Invalid package name 'invalidpackagename:42'"); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testGetTarget() throws Exception { | 
|  | createPkg1(); | 
|  | Label label = Label.parseAbsolute("//pkg1:foo", ImmutableMap.of()); | 
|  | Target target = getTarget(label); | 
|  | assertThat(target.getLabel()).isEqualTo(label); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testGetNonexistentTarget() throws Exception { | 
|  | createPkg1(); | 
|  | NoSuchTargetException e = | 
|  | assertThrows(NoSuchTargetException.class, () -> getTarget("//pkg1:not-there")); | 
|  | assertThat(e) | 
|  | .hasMessageThat() | 
|  | .isEqualTo( | 
|  | "no such target '//pkg1:not-there': target 'not-there' " | 
|  | + "not declared in package 'pkg1' defined by /workspace/pkg1/BUILD"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A missing package is one for which no BUILD file can be found. The PackageCache caches failures | 
|  | * of this kind until the next sync. | 
|  | */ | 
|  | @Test | 
|  | public void testRepeatedAttemptsToParseMissingPackage() throws Exception { | 
|  | checkGetPackageFails("missing", "no such package 'missing': " + "BUILD file not found"); | 
|  |  | 
|  | // Still missing: | 
|  | checkGetPackageFails("missing", "no such package 'missing': " + "BUILD file not found"); | 
|  |  | 
|  | // Update the BUILD file on disk so "missing" is no longer missing: | 
|  | scratch.file("missing/BUILD", "# an ok build file"); | 
|  |  | 
|  | // Still missing: | 
|  | checkGetPackageFails("missing", "no such package 'missing': " + "BUILD file not found"); | 
|  |  | 
|  | invalidatePackages(); | 
|  |  | 
|  | // Found: | 
|  | Package missing = getPackage("missing"); | 
|  |  | 
|  | assertThat(missing.getName()).isEqualTo("missing"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * A broken package is one that exists but contains lexer/parser/evaluator errors. The | 
|  | * PackageCache only makes one attempt to parse each package once found. | 
|  | * | 
|  | * <p>Depending on the strictness of the PackageFactory, parsing a broken package may cause a | 
|  | * Package object to be returned (possibly missing some rules) or an exception to be thrown. For | 
|  | * this test we need that strict behavior. | 
|  | * | 
|  | * <p>Note: since the PackageCache.setStrictPackageCreation method was deleted (since it wasn't | 
|  | * used by any significant clients) creating a "broken" build file got trickier--syntax errors are | 
|  | * not enough. For now, we create an unreadable BUILD file, which will cause an IOException to be | 
|  | * thrown. This test seems less valuable than it once did. | 
|  | */ | 
|  | @Test | 
|  | public void testParseBrokenPackage() throws Exception { | 
|  | reporter.removeHandler(failFastHandler); | 
|  |  | 
|  | Path brokenBuildFile = scratch.file("broken/BUILD"); | 
|  | brokenBuildFile.setReadable(false); | 
|  |  | 
|  | BuildFileContainsErrorsException e = | 
|  | assertThrows(BuildFileContainsErrorsException.class, () -> getPackage("broken")); | 
|  | assertThat(e).hasMessageThat().contains("/workspace/broken/BUILD (Permission denied)"); | 
|  | eventCollector.clear(); | 
|  |  | 
|  | // Update the BUILD file on disk so "broken" is no longer broken: | 
|  | scratch.overwriteFile("broken/BUILD", "# an ok build file"); | 
|  |  | 
|  | invalidatePackages(); //  resets cache of failures | 
|  |  | 
|  | Package broken = getPackage("broken"); | 
|  | assertThat(broken.getName()).isEqualTo("broken"); | 
|  | assertNoEvents(); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testMovedBuildFileCausesReloadAfterSync() throws Exception { | 
|  | // PackageLoader doesn't support --package_path. | 
|  | initializeSkyframeExecutor(/*doPackageLoadingChecks=*/ false); | 
|  |  | 
|  | Path buildFile1 = scratch.file("pkg/BUILD", "cc_library(name = 'foo')"); | 
|  | Path buildFile2 = scratch.file("/otherroot/pkg/BUILD", "cc_library(name = 'bar')"); | 
|  | setOptions("--package_path=/workspace:/otherroot"); | 
|  |  | 
|  | Package oldPkg = getPackage("pkg"); | 
|  | assertThat(getPackage("pkg")).isSameInstanceAs(oldPkg); // change not yet visible | 
|  | assertThat(oldPkg.getFilename().asPath()).isEqualTo(buildFile1); | 
|  | assertThat(oldPkg.getSourceRoot().get()).isEqualTo(Root.fromPath(rootDirectory)); | 
|  |  | 
|  | buildFile1.delete(); | 
|  | invalidatePackages(); | 
|  |  | 
|  | Package newPkg = getPackage("pkg"); | 
|  | assertThat(newPkg).isNotSameInstanceAs(oldPkg); | 
|  | assertThat(newPkg.getFilename().asPath()).isEqualTo(buildFile2); | 
|  | assertThat(newPkg.getSourceRoot().get()).isEqualTo(Root.fromPath(scratch.dir("/otherroot"))); | 
|  |  | 
|  | // TODO(bazel-team): (2009) test BUILD file moves in the other direction too. | 
|  | } | 
|  |  | 
|  | private Path rootDir1; | 
|  | private Path rootDir2; | 
|  |  | 
|  | private void setUpCacheWithTwoRootLocator() throws Exception { | 
|  | // Root 1: | 
|  | //   /a/BUILD | 
|  | //   /b/BUILD | 
|  | //   /c/d | 
|  | //   /c/e | 
|  | // | 
|  | // Root 2: | 
|  | //   /b/BUILD | 
|  | //   /c/BUILD | 
|  | //   /c/d/BUILD | 
|  | //   /f/BUILD | 
|  | //   /f/g | 
|  | //   /f/g/h/BUILD | 
|  |  | 
|  | rootDir1 = scratch.dir("/workspace"); | 
|  | rootDir2 = scratch.dir("/otherroot"); | 
|  |  | 
|  | createBuildFile(rootDir1, "a", "foo.txt", "bar/foo.txt"); | 
|  | createBuildFile(rootDir1, "b", "foo.txt", "bar/foo.txt"); | 
|  |  | 
|  | rootDir1.getRelative("c").createDirectory(); | 
|  | rootDir1.getRelative("c/d").createDirectory(); | 
|  | rootDir1.getRelative("c/e").createDirectory(); | 
|  |  | 
|  | createBuildFile(rootDir2, "c", "d", "d/foo.txt", "foo.txt", "bar/foo.txt", "e", "e/foo.txt"); | 
|  | createBuildFile(rootDir2, "c/d", "foo.txt"); | 
|  | createBuildFile(rootDir2, "f", "g/foo.txt", "g/h", "g/h/foo.txt", "foo.txt"); | 
|  | createBuildFile(rootDir2, "f/g/h", "foo.txt"); | 
|  |  | 
|  | setOptions("--package_path=/workspace:/otherroot"); | 
|  | } | 
|  |  | 
|  | protected Path createBuildFile(Path workspace, String packageName, String... targets) | 
|  | throws IOException { | 
|  | String[] lines = new String[targets.length]; | 
|  |  | 
|  | for (int i = 0; i < targets.length; i++) { | 
|  | lines[i] = "sh_library(name='" + targets[i] + "')"; | 
|  | } | 
|  |  | 
|  | return scratch.file(workspace + "/" + packageName + "/BUILD", lines); | 
|  | } | 
|  |  | 
|  | private void assertLabelValidity(boolean expected, String labelString) throws Exception { | 
|  | Label label = Label.parseAbsolute(labelString, ImmutableMap.of()); | 
|  |  | 
|  | boolean actual = false; | 
|  | String error = null; | 
|  | try { | 
|  | getTarget(label); | 
|  | actual = true; | 
|  | } catch (NoSuchPackageException | NoSuchTargetException e) { | 
|  | error = e.getMessage(); | 
|  | } | 
|  | if (actual != expected) { | 
|  | fail( | 
|  | "assertLabelValidity(" | 
|  | + label | 
|  | + ") " | 
|  | + actual | 
|  | + ", not equal to expected value " | 
|  | + expected | 
|  | + " (error=" | 
|  | + error | 
|  | + ")"); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void assertPackageLoadingFails(String pkgName, String expectedError) throws Exception { | 
|  | Package pkg = getPackage(pkgName); | 
|  | assertThat(pkg.containsErrors()).isTrue(); | 
|  | assertContainsEvent(expectedError); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testLocationForLabelCrossingSubpackage() throws Exception { | 
|  | scratch.file("e/f/BUILD"); | 
|  | scratch.file("e/BUILD", "# Whatever", "filegroup(name='fg', srcs=['f/g'])"); | 
|  | reporter.removeHandler(failFastHandler); | 
|  |  | 
|  | getPackage("e"); | 
|  |  | 
|  | assertThat(eventCollector).hasSize(1); | 
|  | assertThat(Iterables.getOnlyElement(eventCollector).getLocation().line()).isEqualTo(2); | 
|  | } | 
|  |  | 
|  | /** Static tests (i.e. no changes to filesystem, nor calls to sync). */ | 
|  | @Test | 
|  | public void testLabelValidity() throws Exception { | 
|  | // PackageLoader doesn't support --package_path. | 
|  | initializeSkyframeExecutor(/*doPackageLoadingChecks=*/ false); | 
|  |  | 
|  | reporter.removeHandler(failFastHandler); | 
|  | setUpCacheWithTwoRootLocator(); | 
|  |  | 
|  | scratch.file(rootDir2 + "/c/d/foo.txt"); | 
|  |  | 
|  | assertLabelValidity(true, "//a:foo.txt"); | 
|  | assertLabelValidity(true, "//a:bar/foo.txt"); | 
|  | assertLabelValidity(false, "//a/bar:foo.txt"); //  no such package a/bar | 
|  |  | 
|  | assertLabelValidity(true, "//b:foo.txt"); | 
|  | assertLabelValidity(true, "//b:bar/foo.txt"); | 
|  | assertLabelValidity(false, "//b/bar:foo.txt"); // no such package b/bar | 
|  |  | 
|  | assertLabelValidity(true, "//c:foo.txt"); | 
|  | assertLabelValidity(true, "//c:bar/foo.txt"); | 
|  | assertLabelValidity(false, "//c/bar:foo.txt"); // no such package c/bar | 
|  |  | 
|  | assertLabelValidity(true, "//c:foo.txt"); | 
|  |  | 
|  | assertLabelValidity(false, "//c:d/foo.txt"); // crosses boundary of c/d | 
|  | assertLabelValidity(true, "//c/d:foo.txt"); | 
|  |  | 
|  | assertLabelValidity(true, "//c:foo.txt"); | 
|  | assertLabelValidity(true, "//c:e"); | 
|  | assertLabelValidity(true, "//c:e/foo.txt"); | 
|  | assertLabelValidity(false, "//c/e:foo.txt"); // no such package c/e | 
|  |  | 
|  | assertLabelValidity(true, "//f:foo.txt"); | 
|  | assertLabelValidity(true, "//f:g/foo.txt"); | 
|  | assertLabelValidity(false, "//f/g:foo.txt"); // no such package f/g | 
|  | assertLabelValidity(false, "//f:g/h/foo.txt"); // crosses boundary of f/g/h | 
|  | assertLabelValidity(false, "//f/g:h/foo.txt"); // no such package f/g | 
|  | assertLabelValidity(true, "//f/g/h:foo.txt"); | 
|  | } | 
|  |  | 
|  | /** Dynamic tests of label validity. */ | 
|  | @Test | 
|  | public void testAddedBuildFileCausesLabelToBecomeInvalid() throws Exception { | 
|  | reporter.removeHandler(failFastHandler); | 
|  | scratch.file("pkg/BUILD", "cc_library(name = 'foo', srcs = ['x/y.cc'])"); | 
|  |  | 
|  | assertLabelValidity(true, "//pkg:x/y.cc"); | 
|  |  | 
|  | // The existence of this file makes 'x/y.cc' an invalid reference. | 
|  | scratch.file("pkg/x/BUILD"); | 
|  |  | 
|  | // but not yet... | 
|  | assertLabelValidity(true, "//pkg:x/y.cc"); | 
|  |  | 
|  | invalidatePackages(); | 
|  |  | 
|  | // now: | 
|  | assertPackageLoadingFails( | 
|  | "pkg", "Label '//pkg:x/y.cc' is invalid because 'pkg/x' is a subpackage"); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testDeletedPackages() throws Exception { | 
|  | // PackageLoader doesn't support --deleted_packages. | 
|  | initializeSkyframeExecutor(/*doPackageLoadingChecks=*/ false); | 
|  | reporter.removeHandler(failFastHandler); | 
|  | setUpCacheWithTwoRootLocator(); | 
|  | createBuildFile(rootDir1, "c", "d/x"); | 
|  | // Now package c exists in both roots, and c/d exists in only in the second | 
|  | // root.  It's as if we've merged c and c/d in the first root. | 
|  |  | 
|  | // c/d is still a subpackage--found in the second root: | 
|  | assertThat(getPackage("c/d").getFilename().asPath()) | 
|  | .isEqualTo(rootDir2.getRelative("c/d/BUILD")); | 
|  |  | 
|  | // Subpackage labels are still valid... | 
|  | assertLabelValidity(true, "//c/d:foo.txt"); | 
|  | // ...and this crosses package boundaries: | 
|  | assertLabelValidity(false, "//c:d/x"); | 
|  | assertPackageLoadingFails( | 
|  | "c", | 
|  | "Label '//c:d/x' is invalid because 'c/d' is a subpackage; have you deleted c/d/BUILD? " | 
|  | + "If so, use the --deleted_packages=c/d option"); | 
|  |  | 
|  | assertThat(getPackageManager().isPackage(reporter, PackageIdentifier.createInMainRepo("c/d"))) | 
|  | .isTrue(); | 
|  |  | 
|  | setOptions("--deleted_packages=c/d"); | 
|  | invalidatePackages(); | 
|  |  | 
|  | assertThat(getPackageManager().isPackage(reporter, PackageIdentifier.createInMainRepo("c/d"))) | 
|  | .isFalse(); | 
|  |  | 
|  | // c/d is no longer a subpackage--even though there's a BUILD file in the | 
|  | // second root: | 
|  | NoSuchPackageException e = assertThrows(NoSuchPackageException.class, () -> getPackage("c/d")); | 
|  | assertThat(e) | 
|  | .hasMessageThat() | 
|  | .isEqualTo( | 
|  | "no such package 'c/d': Package is considered deleted due to --deleted_packages"); | 
|  |  | 
|  | // Labels in the subpackage are no longer valid... | 
|  | assertLabelValidity(false, "//c/d:x"); | 
|  | // ...and now d is just a subdirectory of c: | 
|  | assertLabelValidity(true, "//c:d/x"); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testPackageFeatures() throws Exception { | 
|  | scratch.file( | 
|  | "peach/BUILD", | 
|  | "package(features = ['crosstool_default_false'])", | 
|  | "cc_library(name = 'cc', srcs = ['cc.cc'])"); | 
|  | assertThat(getPackage("peach").getFeatures()).hasSize(1); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testBrokenPackageOnMultiplePackagePathEntries() throws Exception { | 
|  | reporter.removeHandler(failFastHandler); | 
|  | setOptions("--package_path=.:."); | 
|  | scratch.file("x/y/BUILD"); | 
|  | scratch.file("x/BUILD", "genrule(name = 'x',", "srcs = [],", "outs = ['y/z.h'],", "cmd  = '')"); | 
|  | Package p = getPackage("x"); | 
|  | assertThat(p.containsErrors()).isTrue(); | 
|  | } | 
|  | } |