Open source skyframe tests
--
MOS_MIGRATED_REVID=107983315
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
new file mode 100644
index 0000000..8fe6c9d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageFunctionTest.java
@@ -0,0 +1,557 @@
+// 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.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.analysis.util.BuildViewTestCase;
+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.Preprocessor;
+import com.google.devtools.build.lib.packages.util.SubincludePreprocessor;
+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.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.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 java.io.IOException;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+/**
+ * Unit tests of specific functionality of PackageFunction. Note that it's already tested
+ * indirectly in several other places.
+ */
+public class PackageFunctionTest extends BuildViewTestCase {
+
+ private CustomInMemoryFs fs = new CustomInMemoryFs(new ManualClock());
+
+ @Override
+ protected Preprocessor.Factory.Supplier getPreprocessorFactorySupplier() {
+ return new SubincludePreprocessor.FactorySupplier(scratch.getFileSystem());
+ }
+
+ @Override
+ protected FileSystem createFileSystem() {
+ return fs;
+ }
+
+ private PackageValue validPackage(SkyKey skyKey) throws InterruptedException {
+ EvaluationResult<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ if (result.hasError()) {
+ fail(result.getError(skyKey).getException().getMessage());
+ }
+ PackageValue value = result.get(skyKey);
+ assertFalse(value.getPackage().containsErrors());
+ return value;
+ }
+
+ public void testInconsistentNewPackage() throws Exception {
+ scratch.file("pkg/BUILD", "subinclude('//foo:sub')");
+ scratch.file("foo/sub");
+
+ getSkyframeExecutor().preparePackageLoading(
+ new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)),
+ ConstantRuleVisibility.PUBLIC, true,
+ 7, "", UUID.randomUUID());
+
+ SkyKey pkgLookupKey = PackageLookupValue.key(new PathFragment("foo"));
+ EvaluationResult<PackageLookupValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), pkgLookupKey, /*keepGoing=*/false, reporter);
+ assertFalse(result.hasError());
+ assertFalse(result.get(pkgLookupKey).packageExists());
+
+ scratch.file("foo/BUILD");
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("pkg"));
+ result = SkyframeExecutorTestUtils.evaluate(getSkyframeExecutor(),
+ skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ Throwable exception = result.getError(skyKey).getException();
+ assertThat(exception.getMessage()).contains("Inconsistent filesystem operations");
+ assertThat(exception.getMessage()).contains("Unexpected package");
+ }
+
+ public void testInconsistentMissingPackage() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ Path root1 = fs.getPath("/root1");
+ scratch.file("/root1/WORKSPACE");
+ scratch.file("/root1/foo/sub");
+ scratch.file("/root1/pkg/BUILD", "subinclude('//foo:sub')");
+
+ Path root2 = fs.getPath("/root2");
+ scratch.file("/root2/foo/BUILD");
+ scratch.file("/root2/foo/sub");
+
+ getSkyframeExecutor().preparePackageLoading(
+ new PathPackageLocator(outputBase, ImmutableList.of(root1, root2)),
+ ConstantRuleVisibility.PUBLIC, true,
+ 7, "", UUID.randomUUID());
+
+ SkyKey pkgLookupKey = PackageLookupValue.key(PackageIdentifier.parse("foo"));
+ EvaluationResult<PackageLookupValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), pkgLookupKey, /*keepGoing=*/false, reporter);
+ assertFalse(result.hasError());
+ assertEquals(root2, result.get(pkgLookupKey).getRoot());
+
+ scratch.file("/root1/foo/BUILD");
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("pkg"));
+ result = SkyframeExecutorTestUtils.evaluate(getSkyframeExecutor(),
+ skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ Throwable exception = result.getError(skyKey).getException();
+ System.out.println("exception: " + exception.getMessage());
+ assertThat(exception.getMessage()).contains("Inconsistent filesystem operations");
+ assertThat(exception.getMessage()).contains("Inconsistent package location");
+ }
+
+ public void testPropagatesFilesystemInconsistencies() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ RecordingDifferencer differencer = getSkyframeExecutor().getDifferencerForTesting();
+ Path 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,
+ getSkyframeExecutor().getTimestampGranularityMonitorForTesting());
+ 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<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ String errorMessage = errorInfo.getException().getMessage();
+ assertThat(errorMessage).contains("Inconsistent filesystem operations");
+ assertThat(errorMessage).contains(expectedMessage);
+ }
+
+ public void testPropagatesFilesystemInconsistencies_Globbing() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ RecordingDifferencer differencer = getSkyframeExecutor().getDifferencerForTesting();
+ Path 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();
+
+ long bazFileNodeId = bazFile.stat().getNodeId();
+ // 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,
+ getSkyframeExecutor().getTimestampGranularityMonitorForTesting());
+ 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 = "Some filesystem operations implied /workspace/foo/bar/baz/baz.sh was "
+ + "a regular file with size of 0 and mtime of 0 and nodeId of " + bazFileNodeId + " and "
+ + "mtime of 0 but others made us think it was a nonexistent path";
+ EvaluationResult<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ 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. */
+ 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<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ String errorMessage = errorInfo.getException().getMessage();
+ assertThat(errorMessage).contains("Inconsistent filesystem operations");
+ assertThat(errorMessage).contains(expectedMessage);
+ }
+
+ public void testMultipleSubincludesFromSamePackage() throws Exception {
+ scratch.file("foo/BUILD",
+ "subinclude('//bar:a')",
+ "subinclude('//bar:b')");
+ scratch.file("bar/BUILD",
+ "exports_files(['a', 'b'])");
+ scratch.file("bar/a");
+ scratch.file("bar/b");
+
+ getSkyframeExecutor().preparePackageLoading(
+ new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)),
+ ConstantRuleVisibility.PUBLIC, true,
+ 7, "", UUID.randomUUID());
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("foo"));
+ validPackage(skyKey);
+ }
+
+ public void testTransitiveSubincludesStoredInPackage() throws Exception {
+ scratch.file("foo/BUILD",
+ "subinclude('//bar:a')");
+ scratch.file("bar/BUILD",
+ "exports_files(['a'])");
+ scratch.file("bar/a",
+ "subinclude('//baz:b')");
+ scratch.file("baz/BUILD",
+ "exports_files(['b', 'c'])");
+ scratch.file("baz/b");
+ scratch.file("baz/c");
+
+ getSkyframeExecutor().preparePackageLoading(
+ new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)),
+ ConstantRuleVisibility.PUBLIC, true,
+ 7, "", UUID.randomUUID());
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("foo"));
+ PackageValue value = validPackage(skyKey);
+ assertThat(value.getPackage().getSubincludeLabels()).containsExactly(
+ Label.parseAbsolute("//bar:a"), Label.parseAbsolute("//baz:b"));
+
+ scratch.overwriteFile("bar/a",
+ "subinclude('//baz:c')");
+ getSkyframeExecutor().invalidateFilesUnderPathForTesting(reporter,
+ ModifiedFileSet.builder().modify(new PathFragment("bar/a")).build(), rootDirectory);
+
+ value = validPackage(skyKey);
+ assertThat(value.getPackage().getSubincludeLabels()).containsExactly(
+ Label.parseAbsolute("//bar:a"), Label.parseAbsolute("//baz:c"));
+ }
+
+ public void testTransitiveSkylarkDepsStoredInPackage() throws Exception {
+ scratch.file("foo/BUILD",
+ "load('/bar/ext', 'a')");
+ scratch.file("bar/BUILD");
+ scratch.file("bar/ext.bzl",
+ "load('/baz/ext', 'b')",
+ "a = b");
+ scratch.file("baz/BUILD");
+ scratch.file("baz/ext.bzl",
+ "b = 1");
+ scratch.file("qux/BUILD");
+ scratch.file("qux/ext.bzl",
+ "c = 1");
+
+ getSkyframeExecutor().preparePackageLoading(
+ new PathPackageLocator(outputBase, ImmutableList.of(rootDirectory)),
+ ConstantRuleVisibility.PUBLIC, true,
+ 7, "", UUID.randomUUID());
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("foo"));
+ PackageValue value = validPackage(skyKey);
+ assertThat(value.getPackage().getSkylarkFileDependencies()).containsExactly(
+ Label.parseAbsolute("//bar:ext.bzl"), Label.parseAbsolute("//baz:ext.bzl"));
+
+ scratch.overwriteFile("bar/ext.bzl",
+ "load('/qux/ext', 'c')",
+ "a = c");
+ getSkyframeExecutor().invalidateFilesUnderPathForTesting(reporter,
+ ModifiedFileSet.builder().modify(new PathFragment("bar/ext.bzl")).build(), rootDirectory);
+
+ value = validPackage(skyKey);
+ assertThat(value.getPackage().getSkylarkFileDependencies()).containsExactly(
+ Label.parseAbsolute("//bar:ext.bzl"), Label.parseAbsolute("//qux:ext.bzl"));
+ }
+
+ public void testNonExistingSkylarkExtension() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("test/skylark/BUILD",
+ "load('/test/skylark/bad_extension', 'some_symbol')",
+ "genrule(name = gr,",
+ " outs = ['out.txt'],",
+ " cmd = 'echo hello >@')");
+ invalidatePackages();
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("test/skylark"));
+ EvaluationResult<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ assertThat(errorInfo.getException())
+ .hasMessage("error loading package 'test/skylark': Extension file not found. "
+ + "Unable to load file '//test/skylark:bad_extension.bzl': "
+ + "file doesn't exist or isn't a file");
+ }
+
+ public void testNonExistingSkylarkExtensionWithPythonPreprocessing() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("foo/BUILD",
+ "exports_files(['a'])");
+ scratch.file("foo/a",
+ "load('/test/skylark/bad_extension', 'some_symbol')");
+ scratch.file("test/skylark/BUILD",
+ "subinclude('//foo:a')");
+ invalidatePackages();
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("test/skylark"));
+ EvaluationResult<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ assertContainsEvent("Extension file not found. "
+ + "Unable to load file '//test/skylark:bad_extension.bzl': "
+ + "file doesn't exist or isn't a file");
+ }
+
+ public void testNonExistingSkylarkExtensionFromExtension() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("test/skylark/extension.bzl",
+ "load('/test/skylark/bad_extension', 'some_symbol')",
+ "a = 'a'");
+ scratch.file("test/skylark/BUILD",
+ "load('/test/skylark/extension', 'a')",
+ "genrule(name = gr,",
+ " outs = ['out.txt'],",
+ " cmd = 'echo hello >@')");
+ invalidatePackages();
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("test/skylark"));
+ EvaluationResult<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ assertThat(errorInfo.getException())
+ .hasMessage("error loading package 'test/skylark': Extension file not found. "
+ + "Unable to load file '//test/skylark:bad_extension.bzl': "
+ + "file doesn't exist or isn't a file");
+ }
+
+ public void testSymlinkCycleWithSkylarkExtension() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ Path extensionFilePath = scratch.resolve("/workspace/test/skylark/extension.bzl");
+ FileSystemUtils.ensureSymbolicLink(extensionFilePath, new PathFragment("extension.bzl"));
+ scratch.file("test/skylark/BUILD",
+ "load('/test/skylark/extension', 'a')",
+ "genrule(name = gr,",
+ " outs = ['out.txt'],",
+ " cmd = 'echo hello >@')");
+ invalidatePackages();
+
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("test/skylark"));
+ EvaluationResult<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ ErrorInfo errorInfo = result.getError(skyKey);
+ assertEquals(skyKey, errorInfo.getRootCauseOfException());
+ assertThat(errorInfo.getException())
+ .hasMessage(
+ "error loading package 'test/skylark': Encountered error while reading extension "
+ + "file 'test/skylark/extension.bzl': Symlink cycle");
+ }
+
+ public void testIOErrorLookingForSubpackageForLabelIsHandled() throws Exception {
+ reporter.removeHandler(failFastHandler);
+ scratch.file("foo/BUILD",
+ "sh_library(name = 'foo', srcs = ['bar/baz.sh'])");
+ Path barBuildFile = scratch.file("foo/bar/BUILD");
+ fs.stubStatError(barBuildFile, new IOException("nope"));
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.parse("foo"));
+ EvaluationResult<PackageValue> result = SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/false, reporter);
+ assertTrue(result.hasError());
+ assertContainsEvent("nope");
+ }
+
+ public void testLoadRelativePath() throws Exception {
+ scratch.file("pkg/BUILD", "load('ext', 'a')");
+ scratch.file("pkg/ext.bzl", "a = 1");
+ validPackage(PackageValue.key(PackageIdentifier.parse("pkg")));
+ }
+
+ public void testLoadAbsolutePath() throws Exception {
+ scratch.file("pkg1/BUILD");
+ scratch.file("pkg2/BUILD",
+ "load('/pkg1/ext', 'a')");
+ scratch.file("pkg1/ext.bzl", "a = 1");
+ validPackage(PackageValue.key(PackageIdentifier.parse("pkg2")));
+ }
+
+ public void testBadWorkspaceFile() throws Exception {
+ Path workspacePath = scratch.overwriteFile("WORKSPACE", "junk");
+ SkyKey skyKey = PackageValue.key(PackageIdentifier.createInDefaultRepo("external"));
+ getSkyframeExecutor()
+ .invalidate(
+ Predicates.equalTo(
+ FileStateValue.key(
+ RootedPath.toRootedPath(
+ workspacePath.getParentDirectory(),
+ new PathFragment(workspacePath.getBaseName())))));
+
+ reporter.removeHandler(failFastHandler);
+ EvaluationResult<PackageValue> result =
+ SkyframeExecutorTestUtils.evaluate(
+ getSkyframeExecutor(), skyKey, /*keepGoing=*/ false, reporter);
+ assertFalse(result.hasError());
+ assertTrue(result.get(skyKey).getPackage().containsErrors());
+ }
+
+ private static class CustomInMemoryFs extends InMemoryFileSystem {
+ private abstract static class FileStatusOrException {
+ abstract FileStatus get() throws IOException;
+
+ private static class ExceptionImpl extends FileStatusOrException {
+ private final IOException exn;
+
+ private ExceptionImpl(IOException exn) {
+ this.exn = exn;
+ }
+
+ @Override
+ FileStatus get() throws IOException {
+ throw exn;
+ }
+ }
+
+ private static class FileStatusImpl extends FileStatusOrException {
+
+ @Nullable
+ private final FileStatus fileStatus;
+
+ private FileStatusImpl(@Nullable FileStatus fileStatus) {
+ this.fileStatus = fileStatus;
+ }
+
+ @Override
+ @Nullable
+ FileStatus get() {
+ return fileStatus;
+ }
+ }
+ }
+
+ private Map<Path, FileStatusOrException> stubbedStats = Maps.newHashMap();
+ private Set<Path> makeUnreadableAfterReaddir = Sets.newHashSet();
+
+ public CustomInMemoryFs(ManualClock manualClock) {
+ super(manualClock);
+ }
+
+ public void stubStat(Path path, @Nullable FileStatus stubbedResult) {
+ stubbedStats.put(path, new FileStatusOrException.FileStatusImpl(stubbedResult));
+ }
+
+ public void stubStatError(Path path, IOException stubbedResult) {
+ stubbedStats.put(path, new FileStatusOrException.ExceptionImpl(stubbedResult));
+ }
+
+ @Override
+ public FileStatus stat(Path path, boolean followSymlinks) throws IOException {
+ if (stubbedStats.containsKey(path)) {
+ return stubbedStats.get(path).get();
+ }
+ return super.stat(path, followSymlinks);
+ }
+
+ public void scheduleMakeUnreadableAfterReaddir(Path path) {
+ makeUnreadableAfterReaddir.add(path);
+ }
+
+ @Override
+ public Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException {
+ Collection<Dirent> result = super.readdir(path, followSymlinks);
+ if (makeUnreadableAfterReaddir.contains(path)) {
+ path.setReadable(false);
+ }
+ return result;
+ }
+ }
+}