aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-10-26 16:57:27 +0000
committerGravatar Florian Weikert <fwe@google.com>2015-10-27 11:48:29 +0000
commit81b9083ef06972b2fa20f6cfb124d1cf41214e2f (patch)
tree66f58a0c9048dbe79ea26c06d17ee6b8d58d208f /src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
parent474496336197c0ab2b9a2c1f632d9b231c7f6acc (diff)
Open source some skyframe/bazel tests.
-- MOS_MIGRATED_REVID=106308990
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java')
-rw-r--r--src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java666
1 files changed, 666 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
new file mode 100644
index 0000000000..d70b98ae9e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
@@ -0,0 +1,666 @@
+// 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 com.google.common.base.Functions;
+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.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.testing.EqualsTester;
+import com.google.devtools.build.lib.cmdline.PackageIdentifier;
+import com.google.devtools.build.lib.events.NullEventHandler;
+import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
+import com.google.devtools.build.lib.skyframe.GlobValue.InvalidGlobPatternException;
+import com.google.devtools.build.lib.testutil.ManualClock;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.util.BlazeClock;
+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.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.RootedPath;
+import com.google.devtools.build.lib.vfs.UnixGlob;
+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.InMemoryMemoizingEvaluator;
+import com.google.devtools.build.skyframe.MemoizingEvaluator;
+import com.google.devtools.build.skyframe.RecordingDifferencer;
+import com.google.devtools.build.skyframe.SequentialBuildDriver;
+import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunctionName;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+
+import junit.framework.TestCase;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.Nullable;
+
+/**
+ * Tests for {@link GlobFunction}.
+ */
+public abstract class GlobFunctionTest extends TestCase {
+ public static class GlobFunctionAlwaysUseDirListingTest extends GlobFunctionTest {
+ @Override
+ protected boolean alwaysUseDirListing() {
+ return true;
+ }
+ }
+
+ public static class RegularGlobFunctionTest extends GlobFunctionTest {
+ @Override
+ protected boolean alwaysUseDirListing() {
+ return false;
+ }
+ }
+
+ private CustomInMemoryFs fs;
+ private MemoizingEvaluator evaluator;
+ private SequentialBuildDriver driver;
+ private RecordingDifferencer differencer;
+ private Path root;
+ private Path pkgPath;
+ private AtomicReference<PathPackageLocator> pkgLocator;
+ private TimestampGranularityMonitor tsgm;
+
+ private static final PackageIdentifier PKG_PATH_ID = PackageIdentifier.createInDefaultRepo("pkg");
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ fs = new CustomInMemoryFs(new ManualClock());
+ root = fs.getRootDirectory().getRelative("root/workspace");
+ pkgPath = root.getRelative(PKG_PATH_ID.getPackageFragment());
+
+ pkgLocator = new AtomicReference<>(new PathPackageLocator(root));
+ tsgm = new TimestampGranularityMonitor(BlazeClock.instance());
+
+ differencer = new RecordingDifferencer();
+ evaluator = new InMemoryMemoizingEvaluator(createFunctionMap(), differencer);
+ driver = new SequentialBuildDriver(evaluator);
+ PrecomputedValue.BUILD_ID.set(differencer, UUID.randomUUID());
+ PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get());
+
+ createTestFiles();
+ }
+
+ private Map<SkyFunctionName, SkyFunction> createFunctionMap() {
+ AtomicReference<ImmutableSet<PackageIdentifier>> deletedPackages =
+ new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
+ ExternalFilesHelper externalFilesHelper = new ExternalFilesHelper(pkgLocator);
+
+ Map<SkyFunctionName, SkyFunction> skyFunctions = new HashMap<>();
+ skyFunctions.put(SkyFunctions.GLOB, new GlobFunction(alwaysUseDirListing()));
+ skyFunctions.put(
+ SkyFunctions.DIRECTORY_LISTING_STATE,
+ new DirectoryListingStateFunction(externalFilesHelper));
+ skyFunctions.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction());
+ skyFunctions.put(SkyFunctions.PACKAGE_LOOKUP, new PackageLookupFunction(deletedPackages));
+ skyFunctions.put(
+ SkyFunctions.FILE_STATE,
+ new FileStateFunction(
+ new TimestampGranularityMonitor(BlazeClock.instance()), externalFilesHelper));
+ skyFunctions.put(SkyFunctions.FILE, new FileFunction(pkgLocator, tsgm, externalFilesHelper));
+ return skyFunctions;
+ }
+
+ protected abstract boolean alwaysUseDirListing();
+
+ private void createTestFiles() throws IOException {
+ FileSystemUtils.createDirectoryAndParents(pkgPath);
+ FileSystemUtils.createEmptyFile(pkgPath.getRelative("BUILD"));
+ for (String dir :
+ ImmutableList.of(
+ "foo/bar/wiz", "foo/barnacle/wiz", "food/barnacle/wiz", "fool/barnacle/wiz")) {
+ FileSystemUtils.createDirectoryAndParents(pkgPath.getRelative(dir));
+ }
+ FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/bar/wiz/file"));
+
+ // Used for testing the behavior of globbing into nested subpackages.
+ for (String dir : ImmutableList.of("a1/b1/c", "a2/b2/c")) {
+ FileSystemUtils.createDirectoryAndParents(pkgPath.getRelative(dir));
+ }
+ FileSystemUtils.createEmptyFile(pkgPath.getRelative("a2/b2/BUILD"));
+ }
+
+ public void testSimple() throws Exception {
+ assertGlobMatches("food", /* => */ "food");
+ }
+
+ public void testStartsWithStar() throws Exception {
+ assertGlobMatches("*oo", /* => */ "foo");
+ }
+
+ public void testStartsWithStarWithMiddleStar() throws Exception {
+ assertGlobMatches("*f*o", /* => */ "foo");
+ }
+
+ public void testSingleMatchEqual() throws Exception {
+ assertGlobsEqual("*oo", "*f*o"); // both produce "foo"
+ }
+
+ public void testEndsWithStar() throws Exception {
+ assertGlobMatches("foo*", /* => */ "foo", "food", "fool");
+ }
+
+ public void testEndsWithStarWithMiddleStar() throws Exception {
+ assertGlobMatches("f*oo*", /* => */ "foo", "food", "fool");
+ }
+
+ public void testMultipleMatchesEqual() throws Exception {
+ assertGlobsEqual("foo*", "f*oo*"); // both produce "foo", "food", "fool"
+ }
+
+ public void testMiddleStar() throws Exception {
+ assertGlobMatches("f*o", /* => */ "foo");
+ }
+
+ public void testTwoMiddleStars() throws Exception {
+ assertGlobMatches("f*o*o", /* => */ "foo");
+ }
+
+ public void testSingleStarPatternWithNamedChild() throws Exception {
+ assertGlobMatches("*/bar", /* => */ "foo/bar");
+ }
+
+ public void testDeepSubpackages() throws Exception {
+ assertGlobMatches("*/*/c", /* => */ "a1/b1/c");
+ }
+
+ public void testSingleStarPatternWithChildGlob() throws Exception {
+ assertGlobMatches(
+ "*/bar*", /* => */ "foo/bar", "foo/barnacle", "food/barnacle", "fool/barnacle");
+ }
+
+ public void testSingleStarAsChildGlob() throws Exception {
+ assertGlobMatches("foo/*/wiz", /* => */ "foo/bar/wiz", "foo/barnacle/wiz");
+ }
+
+ public void testNoAsteriskAndFilesDontExist() throws Exception {
+ // Note un-UNIX like semantics:
+ assertGlobMatches("ceci/n'est/pas/une/globbe" /* => nothing */);
+ }
+
+ public void testSingleAsteriskUnderNonexistentDirectory() throws Exception {
+ // Note un-UNIX like semantics:
+ assertGlobMatches("not-there/*" /* => nothing */);
+ }
+
+ public void testDifferentGlobsSameResultEqual() throws Exception {
+ // Once the globs are run, it doesn't matter what pattern ran; only the output.
+ assertGlobsEqual("not-there/*", "syzygy/*"); // Both produce nothing.
+ }
+
+ public void testGlobUnderFile() throws Exception {
+ assertGlobMatches("foo/bar/wiz/file/*" /* => nothing */);
+ }
+
+ public void testGlobEqualsHashCode() throws Exception {
+ // Each "equality group" forms a set of elements that are all equals() to one another,
+ // and also produce the same hashCode.
+ new EqualsTester()
+ .addEqualityGroup(runGlob(false, "no-such-file")) // Matches nothing.
+ .addEqualityGroup(runGlob(false, "BUILD"), runGlob(true, "BUILD")) // Matches BUILD.
+ .addEqualityGroup(runGlob(false, "**")) // Matches lots of things.
+ .addEqualityGroup(
+ runGlob(false, "f*o/bar*"),
+ runGlob(false, "foo/bar*")) // Matches foo/bar and foo/barnacle.
+ .testEquals();
+ }
+
+ public void testGlobMissingPackage() throws Exception {
+ // This is a malformed value key, because "missing" is not a package. Nevertheless, we have a
+ // sanity check that building the corresponding GlobValue fails loudly. The test depends on
+ // implementation details of ParallelEvaluator and GlobFunction.
+ SkyKey skyKey =
+ GlobValue.key(
+ PackageIdentifier.createInDefaultRepo("missing"),
+ "foo",
+ false,
+ PathFragment.EMPTY_FRAGMENT);
+ try {
+ driver.evaluate(
+ ImmutableList.of(skyKey),
+ false,
+ SkyframeExecutor.DEFAULT_THREAD_COUNT,
+ NullEventHandler.INSTANCE);
+ fail();
+ } catch (RuntimeException e) {
+ assertThat(e.getMessage())
+ .contains("Unrecoverable error while evaluating node '" + skyKey + "'");
+ Throwable cause = e.getCause();
+ assertThat(cause).isInstanceOf(IllegalStateException.class);
+ assertThat(cause.getMessage()).contains("isn't an existing package");
+ }
+ }
+
+ public void testGlobDoesNotCrossPackageBoundary() throws Exception {
+ FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/BUILD"));
+ // "foo/bar" should not be in the results because foo is a separate package.
+ assertGlobMatches("f*/*", /* => */ "food/barnacle", "fool/barnacle");
+ }
+
+ public void testGlobDirectoryMatchDoesNotCrossPackageBoundary() throws Exception {
+ FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/bar/BUILD"));
+ // "foo/bar" should not be in the results because foo/bar is a separate package.
+ assertGlobMatches("foo/*", /* => */ "foo/barnacle");
+ }
+
+ public void testStarStarDoesNotCrossPackageBoundary() throws Exception {
+ FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/bar/BUILD"));
+ // "foo/bar" should not be in the results because foo/bar is a separate package.
+ assertGlobMatches("foo/**", /* => */ "foo", "foo/barnacle", "foo/barnacle/wiz");
+ }
+
+ private void assertGlobMatches(String pattern, String... expecteds) throws Exception {
+ assertGlobMatches(false, pattern, expecteds);
+ }
+
+ private void assertGlobWithoutDirsMatches(String pattern, String... expecteds) throws Exception {
+ assertGlobMatches(true, pattern, expecteds);
+ }
+
+ private void assertGlobMatches(boolean excludeDirs, String pattern, String... expecteds)
+ throws Exception {
+ MoreAsserts.assertSameContents(
+ ImmutableList.copyOf(expecteds),
+ Iterables.transform(
+ runGlob(excludeDirs, pattern).getMatches(), Functions.toStringFunction()));
+ }
+
+ private void assertGlobsEqual(String pattern1, String pattern2) throws Exception {
+ GlobValue value1 = runGlob(false, pattern1);
+ GlobValue value2 = runGlob(false, pattern2);
+ assertEquals(
+ "GlobValues "
+ + value1.getMatches()
+ + " and "
+ + value2.getMatches()
+ + " should be equal. "
+ + "Patterns: "
+ + pattern1
+ + ","
+ + pattern2,
+ value1,
+ value2);
+ // Just to be paranoid:
+ assertEquals(value1, value1);
+ assertEquals(value2, value2);
+ }
+
+ private GlobValue runGlob(boolean excludeDirs, String pattern) throws Exception {
+ SkyKey skyKey = GlobValue.key(PKG_PATH_ID, pattern, excludeDirs, PathFragment.EMPTY_FRAGMENT);
+ EvaluationResult<SkyValue> result =
+ driver.evaluate(
+ ImmutableList.of(skyKey),
+ false,
+ SkyframeExecutor.DEFAULT_THREAD_COUNT,
+ NullEventHandler.INSTANCE);
+ if (result.hasError()) {
+ throw result.getError().getException();
+ }
+ return (GlobValue) result.get(skyKey);
+ }
+
+ public void testGlobWithoutWildcards() throws Exception {
+ String pattern = "foo/bar/wiz/file";
+
+ assertGlobMatches(pattern, "foo/bar/wiz/file");
+ // Ensure that the glob depends on the FileValue and not on the DirectoryListingValue.
+ pkgPath.getRelative("foo/bar/wiz/file").delete();
+ // Nothing has been invalidated yet, so the cached result is returned.
+ assertGlobMatches(pattern, "foo/bar/wiz/file");
+
+ if (alwaysUseDirListing()) {
+ differencer.invalidate(
+ ImmutableList.of(
+ FileStateValue.key(
+ RootedPath.toRootedPath(root, pkgPath.getRelative("foo/bar/wiz/file")))));
+ // The result should not rely on the FileStateValue, so it's still a cache hit.
+ assertGlobMatches(pattern, "foo/bar/wiz/file");
+
+ differencer.invalidate(
+ ImmutableList.of(
+ DirectoryListingStateValue.key(
+ RootedPath.toRootedPath(root, pkgPath.getRelative("foo/bar/wiz")))));
+ // This should have invalidated the glob result.
+ assertGlobMatches(pattern /* => nothing */);
+ } else {
+ differencer.invalidate(
+ ImmutableList.of(
+ DirectoryListingStateValue.key(
+ RootedPath.toRootedPath(root, pkgPath.getRelative("foo/bar/wiz")))));
+ // The result should not rely on the DirectoryListingValue, so it's still a cache hit.
+ assertGlobMatches(pattern, "foo/bar/wiz/file");
+
+ differencer.invalidate(
+ ImmutableList.of(
+ FileStateValue.key(
+ RootedPath.toRootedPath(root, pkgPath.getRelative("foo/bar/wiz/file")))));
+ // This should have invalidated the glob result.
+ assertGlobMatches(pattern /* => nothing */);
+ }
+ }
+
+ public void testIllegalPatterns() throws Exception {
+ assertIllegalPattern("(illegal) pattern");
+ assertIllegalPattern("[illegal pattern");
+ assertIllegalPattern("}illegal pattern");
+ assertIllegalPattern("foo**bar");
+ assertIllegalPattern("?");
+ assertIllegalPattern("");
+ assertIllegalPattern(".");
+ assertIllegalPattern("/foo");
+ assertIllegalPattern("./foo");
+ assertIllegalPattern("foo/");
+ assertIllegalPattern("foo/./bar");
+ assertIllegalPattern("../foo/bar");
+ assertIllegalPattern("foo//bar");
+ }
+
+ public void testIllegalRecursivePatterns() throws Exception {
+ for (String prefix : Lists.newArrayList("", "*/", "**/", "ba/")) {
+ String suffix = ("/" + prefix).substring(0, prefix.length());
+ for (String pattern : Lists.newArrayList("**fo", "fo**", "**fo**", "fo**fo", "fo**fo**fo")) {
+ assertIllegalPattern(prefix + pattern);
+ assertIllegalPattern(pattern + suffix);
+ }
+ }
+ }
+
+ private void assertIllegalPattern(String pattern) {
+ try {
+ GlobValue.key(PKG_PATH_ID, pattern, false, PathFragment.EMPTY_FRAGMENT);
+ fail("invalid pattern not detected: " + pattern);
+ } catch (InvalidGlobPatternException e) {
+ // Expected.
+ }
+ }
+
+ /**
+ * Tests that globs can contain Java regular expression special characters
+ */
+ public void testSpecialRegexCharacter() throws Exception {
+ Path aDotB = pkgPath.getChild("a.b");
+ FileSystemUtils.createEmptyFile(aDotB);
+ FileSystemUtils.createEmptyFile(pkgPath.getChild("aab"));
+ // Note: this contains two asterisks because otherwise a RE is not built,
+ // as an optimization.
+ assertThat(UnixGlob.forPath(pkgPath).addPattern("*a.b*").globInterruptible())
+ .containsExactly(aDotB);
+ }
+
+ public void testMatchesCallWithNoCache() {
+ assertTrue(UnixGlob.matches("*a*b", "CaCb", null));
+ }
+
+ public void testHiddenFiles() throws Exception {
+ for (String dir : ImmutableList.of(".hidden", "..also.hidden", "not.hidden")) {
+ FileSystemUtils.createDirectoryAndParents(pkgPath.getRelative(dir));
+ }
+ // Note that these are not in the result: ".", ".."
+ assertGlobMatches(
+ "*", "a1", "a2", "not.hidden", "foo", "fool", "food", "BUILD", ".hidden", "..also.hidden");
+ assertGlobMatches("*.hidden", "not.hidden");
+ }
+
+ public void testDoubleStar() throws Exception {
+ assertGlobMatches(
+ "**",
+ "",
+ "BUILD",
+ "a1",
+ "a1/b1",
+ "a1/b1/c",
+ "a2",
+ "foo",
+ "foo/bar",
+ "foo/bar/wiz",
+ "foo/bar/wiz/file",
+ "foo/barnacle",
+ "foo/barnacle/wiz",
+ "food",
+ "food/barnacle",
+ "food/barnacle/wiz",
+ "fool",
+ "fool/barnacle",
+ "fool/barnacle/wiz");
+ }
+
+ public void testDoubleStarExcludeDirs() throws Exception {
+ assertGlobWithoutDirsMatches("**", "BUILD", "foo/bar/wiz/file");
+ }
+
+ public void testDoubleDoubleStar() throws Exception {
+ assertGlobMatches(
+ "**/**",
+ "",
+ "BUILD",
+ "a1",
+ "a1/b1",
+ "a1/b1/c",
+ "a2",
+ "foo",
+ "foo/bar",
+ "foo/bar/wiz",
+ "foo/bar/wiz/file",
+ "foo/barnacle",
+ "foo/barnacle/wiz",
+ "food",
+ "food/barnacle",
+ "food/barnacle/wiz",
+ "fool",
+ "fool/barnacle",
+ "fool/barnacle/wiz");
+ }
+
+ public void testDirectoryWithDoubleStar() throws Exception {
+ assertGlobMatches(
+ "foo/**",
+ "foo",
+ "foo/bar",
+ "foo/bar/wiz",
+ "foo/bar/wiz/file",
+ "foo/barnacle",
+ "foo/barnacle/wiz");
+ }
+
+ public void testDoubleStarPatternWithNamedChild() throws Exception {
+ assertGlobMatches("**/bar", "foo/bar");
+ }
+
+ public void testDoubleStarPatternWithChildGlob() throws Exception {
+ assertGlobMatches("**/ba*", "foo/bar", "foo/barnacle", "food/barnacle", "fool/barnacle");
+ }
+
+ public void testDoubleStarAsChildGlob() throws Exception {
+ FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/barnacle/wiz/wiz"));
+ FileSystemUtils.createDirectoryAndParents(pkgPath.getRelative("foo/barnacle/baz/wiz"));
+
+ assertGlobMatches(
+ "foo/**/wiz",
+ "foo/bar/wiz",
+ "foo/barnacle/baz/wiz",
+ "foo/barnacle/wiz",
+ "foo/barnacle/wiz/wiz");
+ }
+
+ public void testDoubleStarUnderNonexistentDirectory() throws Exception {
+ assertGlobMatches("not-there/**" /* => nothing */);
+ }
+
+ public void testDoubleStarUnderFile() throws Exception {
+ assertGlobMatches("foo/bar/wiz/file/**" /* => nothing */);
+ }
+
+ /** Regression test for b/13319874: Directory listing crash. */
+ public void testResilienceToFilesystemInconsistencies_DirectoryExistence() throws Exception {
+ long nodeId = pkgPath.getRelative("BUILD").stat().getNodeId();
+ // Our custom filesystem says "pkgPath/BUILD" exists but "pkgPath" does not exist.
+ fs.stubStat(pkgPath, null);
+ RootedPath pkgRootedPath = RootedPath.toRootedPath(root, pkgPath);
+ FileStateValue pkgDirFileStateValue = FileStateValue.create(pkgRootedPath, tsgm);
+ FileValue pkgDirValue =
+ FileValue.value(pkgRootedPath, pkgDirFileStateValue, pkgRootedPath, pkgDirFileStateValue);
+ differencer.inject(ImmutableMap.of(FileValue.key(pkgRootedPath), pkgDirValue));
+ String expectedMessage =
+ "Some filesystem operations implied /root/workspace/pkg/BUILD was a "
+ + "regular file with size of 0 and mtime of 0 and nodeId of "
+ + nodeId
+ + " and mtime of 0 "
+ + "but others made us think it was a nonexistent path";
+ SkyKey skyKey = GlobValue.key(PKG_PATH_ID, "*/foo", false, PathFragment.EMPTY_FRAGMENT);
+ EvaluationResult<GlobValue> result =
+ driver.evaluate(
+ ImmutableList.of(skyKey),
+ false,
+ SkyframeExecutor.DEFAULT_THREAD_COUNT,
+ NullEventHandler.INSTANCE);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
+ assertThat(errorInfo.getException().getMessage()).contains(expectedMessage);
+ }
+
+ public void testResilienceToFilesystemInconsistencies_SubdirectoryExistence() throws Exception {
+ // Our custom filesystem says directory "pkgPath/foo/bar" contains a subdirectory "wiz" but a
+ // direct stat on "pkgPath/foo/bar/wiz" says it does not exist.
+ Path fooBarDir = pkgPath.getRelative("foo/bar");
+ fs.stubStat(fooBarDir.getRelative("wiz"), null);
+ RootedPath fooBarDirRootedPath = RootedPath.toRootedPath(root, fooBarDir);
+ SkyValue fooBarDirListingValue =
+ DirectoryListingStateValue.createForTesting(
+ ImmutableList.of(new Dirent("wiz", Dirent.Type.DIRECTORY)));
+ differencer.inject(
+ ImmutableMap.of(
+ DirectoryListingStateValue.key(fooBarDirRootedPath), fooBarDirListingValue));
+ String expectedMessage = "/root/workspace/pkg/foo/bar/wiz is no longer an existing directory.";
+ SkyKey skyKey = GlobValue.key(PKG_PATH_ID, "**/wiz", false, PathFragment.EMPTY_FRAGMENT);
+ EvaluationResult<GlobValue> result =
+ driver.evaluate(
+ ImmutableList.of(skyKey),
+ false,
+ SkyframeExecutor.DEFAULT_THREAD_COUNT,
+ NullEventHandler.INSTANCE);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
+ assertThat(errorInfo.getException().getMessage()).contains(expectedMessage);
+ }
+
+ public void testResilienceToFilesystemInconsistencies_SymlinkType() throws Exception {
+ RootedPath wizRootedPath = RootedPath.toRootedPath(root, pkgPath.getRelative("foo/bar/wiz"));
+ RootedPath fileRootedPath =
+ RootedPath.toRootedPath(root, pkgPath.getRelative("foo/bar/wiz/file"));
+ final FileStatus realStat = fileRootedPath.asPath().stat();
+ fs.stubStat(
+ fileRootedPath.asPath(),
+ new FileStatus() {
+
+ @Override
+ public boolean isFile() {
+ // The stat says foo/bar/wiz/file is a real file, not a symlink.
+ return true;
+ }
+
+ @Override
+ public boolean isSpecialFile() {
+ return false;
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return false;
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ return false;
+ }
+
+ @Override
+ public long getSize() throws IOException {
+ return realStat.getSize();
+ }
+
+ @Override
+ public long getLastModifiedTime() throws IOException {
+ return realStat.getLastModifiedTime();
+ }
+
+ @Override
+ public long getLastChangeTime() throws IOException {
+ return realStat.getLastChangeTime();
+ }
+
+ @Override
+ public long getNodeId() throws IOException {
+ return realStat.getNodeId();
+ }
+ });
+ // But the dir listing say foo/bar/wiz/file is a symlink.
+ SkyValue wizDirListingValue =
+ DirectoryListingStateValue.createForTesting(
+ ImmutableList.of(new Dirent("file", Dirent.Type.SYMLINK)));
+ differencer.inject(
+ ImmutableMap.of(DirectoryListingStateValue.key(wizRootedPath), wizDirListingValue));
+ String expectedMessage =
+ "readdir and stat disagree about whether " + fileRootedPath.asPath() + " is a symlink";
+ SkyKey skyKey = GlobValue.key(PKG_PATH_ID, "foo/bar/wiz/*", false, PathFragment.EMPTY_FRAGMENT);
+ EvaluationResult<GlobValue> result =
+ driver.evaluate(
+ ImmutableList.of(skyKey),
+ false,
+ SkyframeExecutor.DEFAULT_THREAD_COUNT,
+ NullEventHandler.INSTANCE);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
+ assertThat(errorInfo.getException().getMessage()).contains(expectedMessage);
+ }
+
+ private class CustomInMemoryFs extends InMemoryFileSystem {
+
+ private Map<Path, FileStatus> stubbedStats = Maps.newHashMap();
+
+ public CustomInMemoryFs(ManualClock manualClock) {
+ super(manualClock);
+ }
+
+ public void stubStat(Path path, @Nullable FileStatus stubbedResult) {
+ stubbedStats.put(path, stubbedResult);
+ }
+
+ @Override
+ public FileStatus stat(Path path, boolean followSymlinks) throws IOException {
+ if (stubbedStats.containsKey(path)) {
+ return stubbedStats.get(path);
+ }
+ return super.stat(path, followSymlinks);
+ }
+ }
+}