// 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.skyframe; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.devtools.build.lib.actions.FileStateValue; import com.google.devtools.build.lib.actions.FileValue; import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; 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.ConstantRuleVisibility; import com.google.devtools.build.lib.packages.NoSuchPackageException; import com.google.devtools.build.lib.packages.NoSuchTargetException; import com.google.devtools.build.lib.packages.SkylarkSemanticsOptions; import com.google.devtools.build.lib.pkgcache.PackageCacheOptions; import com.google.devtools.build.lib.pkgcache.PathPackageLocator; import com.google.devtools.build.lib.skyframe.util.SkyframeExecutorTestUtils; import com.google.devtools.build.lib.testutil.ManualClock; import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor; import com.google.devtools.build.lib.vfs.Dirent; import com.google.devtools.build.lib.vfs.FileStatus; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.ModifiedFileSet; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.lib.vfs.Root; import com.google.devtools.build.lib.vfs.RootedPath; import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem; import com.google.devtools.build.skyframe.ErrorInfo; import com.google.devtools.build.skyframe.EvaluationResult; import com.google.devtools.build.skyframe.RecordingDifferencer; import com.google.devtools.build.skyframe.SkyKey; import com.google.devtools.build.skyframe.SkyValue; import com.google.devtools.common.options.Options; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.annotation.Nullable; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** * Unit tests of specific functionality of PackageFunction. Note that it's already tested * indirectly in several other places. */ @RunWith(JUnit4.class) public class PackageFunctionTest extends BuildViewTestCase { private CustomInMemoryFs fs = new CustomInMemoryFs(new ManualClock()); private void preparePackageLoading(Path... roots) { PackageCacheOptions packageCacheOptions = Options.getDefaults(PackageCacheOptions.class); packageCacheOptions.defaultVisibility = ConstantRuleVisibility.PUBLIC; packageCacheOptions.showLoadingProgress = true; packageCacheOptions.globbingThreads = 7; getSkyframeExecutor() .preparePackageLoading( new PathPackageLocator( outputBase, Arrays.stream(roots).map(Root::fromPath).collect(ImmutableList.toImmutableList()), BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY), packageCacheOptions, Options.getDefaults(SkylarkSemanticsOptions.class), "", UUID.randomUUID(), ImmutableMap.of(), ImmutableMap.of(), new TimestampGranularityMonitor(BlazeClock.instance())); } @Override protected FileSystem createFileSystem() { return fs; } private PackageValue validPackage(SkyKey skyKey) throws InterruptedException { EvaluationResult result = SkyframeExecutorTestUtils.evaluate( getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter); if (result.hasError()) { fail(result.getError(skyKey).getException().getMessage()); } PackageValue value = result.get(skyKey); assertThat(value.getPackage().containsErrors()).isFalse(); return value; } @Test public void testValidPackage() throws Exception { scratch.file("pkg/BUILD"); validPackage(PackageValue.key(PackageIdentifier.parse("@//pkg"))); } @Test public void testPropagatesFilesystemInconsistencies() throws Exception { reporter.removeHandler(failFastHandler); RecordingDifferencer differencer = getSkyframeExecutor().getDifferencerForTesting(); Root pkgRoot = getSkyframeExecutor().getPathEntries().get(0); Path fooBuildFile = scratch.file("foo/BUILD"); Path fooDir = fooBuildFile.getParentDirectory(); // Our custom filesystem says "foo/BUILD" exists but its parent "foo" is a file. FileStatus inconsistentParentFileStatus = new FileStatus() { @Override public boolean isFile() { return true; } @Override public boolean isDirectory() { return false; } @Override public boolean isSymbolicLink() { return false; } @Override public boolean isSpecialFile() { return false; } @Override public long getSize() throws IOException { return 0; } @Override public long getLastModifiedTime() throws IOException { return 0; } @Override public long getLastChangeTime() throws IOException { return 0; } @Override public long getNodeId() throws IOException { return 0; } }; fs.stubStat(fooDir, inconsistentParentFileStatus); RootedPath pkgRootedPath = RootedPath.toRootedPath(pkgRoot, fooDir); SkyValue fooDirValue = FileStateValue.create(pkgRootedPath, tsgm); differencer.inject(ImmutableMap.of(FileStateValue.key(pkgRootedPath), fooDirValue)); SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("@//foo")); String expectedMessage = "/workspace/foo/BUILD exists but its parent path /workspace/foo isn't " + "an existing directory"; EvaluationResult result = SkyframeExecutorTestUtils.evaluate( getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter); assertThat(result.hasError()).isTrue(); ErrorInfo errorInfo = result.getError(skyKey); String errorMessage = errorInfo.getException().getMessage(); assertThat(errorMessage).contains("Inconsistent filesystem operations"); assertThat(errorMessage).contains(expectedMessage); } @Test public void testPropagatesFilesystemInconsistencies_Globbing() throws Exception { reporter.removeHandler(failFastHandler); RecordingDifferencer differencer = getSkyframeExecutor().getDifferencerForTesting(); Root pkgRoot = getSkyframeExecutor().getPathEntries().get(0); scratch.file("foo/BUILD", "subinclude('//a:a')", "sh_library(name = 'foo', srcs = glob(['bar/**/baz.sh']))"); scratch.file("a/BUILD"); scratch.file("a/a"); Path bazFile = scratch.file("foo/bar/baz/baz.sh"); Path bazDir = bazFile.getParentDirectory(); Path barDir = bazDir.getParentDirectory(); // Our custom filesystem says "foo/bar/baz" does not exist but it also says that "foo/bar" // has a child directory "baz". fs.stubStat(bazDir, null); RootedPath barDirRootedPath = RootedPath.toRootedPath(pkgRoot, barDir); FileStateValue barDirFileStateValue = FileStateValue.create(barDirRootedPath, tsgm); FileValue barDirFileValue = FileValue.value(barDirRootedPath, barDirFileStateValue, barDirRootedPath, barDirFileStateValue); DirectoryListingValue barDirListing = DirectoryListingValue.value(barDirRootedPath, barDirFileValue, DirectoryListingStateValue.create(ImmutableList.of( new Dirent("baz", Dirent.Type.DIRECTORY)))); differencer.inject(ImmutableMap.of(DirectoryListingValue.key(barDirRootedPath), barDirListing)); SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("@//foo")); String expectedMessage = "/workspace/foo/bar/baz is no longer an existing directory"; EvaluationResult result = SkyframeExecutorTestUtils.evaluate( getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter); assertThat(result.hasError()).isTrue(); ErrorInfo errorInfo = result.getError(skyKey); String errorMessage = errorInfo.getException().getMessage(); assertThat(errorMessage).contains("Inconsistent filesystem operations"); assertThat(errorMessage).contains(expectedMessage); } /** Regression test for unexpected exception type from PackageValue. */ @Test public void testDiscrepancyBetweenLegacyAndSkyframePackageLoadingErrors() throws Exception { reporter.removeHandler(failFastHandler); Path fooBuildFile = scratch.file("foo/BUILD", "sh_library(name = 'foo', srcs = glob(['bar/*.sh']))"); Path fooDir = fooBuildFile.getParentDirectory(); Path barDir = fooDir.getRelative("bar"); scratch.file("foo/bar/baz.sh"); fs.scheduleMakeUnreadableAfterReaddir(barDir); SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("@//foo")); String expectedMessage = "Encountered error 'Directory is not readable'"; EvaluationResult result = SkyframeExecutorTestUtils.evaluate( getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter); assertThat(result.hasError()).isTrue(); ErrorInfo errorInfo = result.getError(skyKey); String errorMessage = errorInfo.getException().getMessage(); assertThat(errorMessage).contains("Inconsistent filesystem operations"); assertThat(errorMessage).contains(expectedMessage); } @SuppressWarnings("unchecked") // Cast of srcs attribute to Iterable