blob: cd6fbec712b6540bb52f173e4525c4081416bf40 [file] [log] [blame]
// 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.Functions;
import com.google.common.base.Optional;
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.actions.FileStateValue;
import com.google.devtools.build.lib.actions.FileValue;
import com.google.devtools.build.lib.actions.InconsistentFilesystemException;
import com.google.devtools.build.lib.analysis.BlazeDirectories;
import com.google.devtools.build.lib.analysis.ServerDirectories;
import com.google.devtools.build.lib.analysis.util.AnalysisMock;
import com.google.devtools.build.lib.cmdline.PackageIdentifier;
import com.google.devtools.build.lib.events.NullEventHandler;
import com.google.devtools.build.lib.packages.PackageFactory;
import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension;
import com.google.devtools.build.lib.packages.RuleClassProvider;
import com.google.devtools.build.lib.packages.WorkspaceFileValue;
import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
import com.google.devtools.build.lib.skyframe.GlobValue.InvalidGlobPatternException;
import com.google.devtools.build.lib.skyframe.PackageLookupFunction.CrossRepositoryLabelViolationStrategy;
import com.google.devtools.build.lib.syntax.StarlarkSemantics;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.testutil.TestConstants;
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.Root;
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.EvaluationContext;
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.SequencedRecordingDifferencer;
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 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;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link GlobFunction}.
*/
public abstract class GlobFunctionTest {
private static final EvaluationContext EVALUATION_OPTIONS =
EvaluationContext.newBuilder()
.setKeepGoing(false)
.setNumThreads(SkyframeExecutor.DEFAULT_THREAD_COUNT)
.setEventHander(NullEventHandler.INSTANCE)
.build();
@RunWith(JUnit4.class)
public static class GlobFunctionAlwaysUseDirListingTest extends GlobFunctionTest {
@Override
protected boolean alwaysUseDirListing() {
return true;
}
}
@RunWith(JUnit4.class)
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 writableRoot;
private Path outputBase;
private Path pkgPath;
private AtomicReference<PathPackageLocator> pkgLocator;
private static final PackageIdentifier PKG_ID = PackageIdentifier.createInMainRepo("pkg");
@Before
public final void setUp() throws Exception {
fs = new CustomInMemoryFs(new ManualClock());
root = fs.getPath("/root/workspace");
writableRoot = fs.getPath("/writableRoot/workspace");
outputBase = fs.getPath("/output_base");
pkgPath = root.getRelative(PKG_ID.getPackageFragment());
pkgLocator =
new AtomicReference<>(
new PathPackageLocator(
outputBase,
ImmutableList.of(Root.fromPath(writableRoot), Root.fromPath(root)),
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
differencer = new SequencedRecordingDifferencer();
evaluator = new InMemoryMemoizingEvaluator(createFunctionMap(), differencer);
driver = new SequentialBuildDriver(evaluator);
PrecomputedValue.BUILD_ID.set(differencer, UUID.randomUUID());
PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get());
PrecomputedValue.STARLARK_SEMANTICS.set(differencer, StarlarkSemantics.DEFAULT_SEMANTICS);
RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE.set(
differencer, Optional.<RootedPath>absent());
createTestFiles();
}
private Map<SkyFunctionName, SkyFunction> createFunctionMap() {
AtomicReference<ImmutableSet<PackageIdentifier>> deletedPackages =
new AtomicReference<>(ImmutableSet.<PackageIdentifier>of());
BlazeDirectories directories =
new BlazeDirectories(
new ServerDirectories(root, root, root),
root,
/* defaultSystemJavabase= */ null,
TestConstants.PRODUCT_NAME);
ExternalFilesHelper externalFilesHelper =
ExternalFilesHelper.createForTesting(
pkgLocator,
ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
directories);
Map<SkyFunctionName, SkyFunction> skyFunctions = new HashMap<>();
skyFunctions.put(SkyFunctions.GLOB, new GlobFunction(alwaysUseDirListing()));
skyFunctions.put(
SkyFunctions.DIRECTORY_LISTING_STATE,
new DirectoryListingStateFunction(
externalFilesHelper, new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS)));
skyFunctions.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction());
skyFunctions.put(
SkyFunctions.PACKAGE_LOOKUP,
new PackageLookupFunction(
deletedPackages,
CrossRepositoryLabelViolationStrategy.ERROR,
BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
skyFunctions.put(SkyFunctions.BLACKLISTED_PACKAGE_PREFIXES,
new BlacklistedPackagePrefixesFunction(
/*hardcodedBlacklistedPackagePrefixes=*/ ImmutableSet.of(),
/*additionalBlacklistedPackagePrefixesFile=*/ PathFragment.EMPTY_FRAGMENT));
skyFunctions.put(
FileStateValue.FILE_STATE,
new FileStateFunction(
new AtomicReference<TimestampGranularityMonitor>(), externalFilesHelper));
skyFunctions.put(FileValue.FILE, new FileFunction(pkgLocator));
skyFunctions.put(SkyFunctions.DIRECTORY_LISTING, new DirectoryListingFunction());
skyFunctions.put(
SkyFunctions.DIRECTORY_LISTING_STATE,
new DirectoryListingStateFunction(
externalFilesHelper, new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS)));
AnalysisMock analysisMock = AnalysisMock.get();
RuleClassProvider ruleClassProvider = analysisMock.createRuleClassProvider();
skyFunctions.put(SkyFunctions.WORKSPACE_AST, new WorkspaceASTFunction(ruleClassProvider));
skyFunctions.put(
WorkspaceFileValue.WORKSPACE_FILE,
new WorkspaceFileFunction(
ruleClassProvider,
analysisMock
.getPackageFactoryBuilderForTesting(directories)
.setEnvironmentExtensions(
ImmutableList.<EnvironmentExtension>of(
new PackageFactory.EmptyEnvironmentExtension()))
.build(ruleClassProvider, fs),
directories,
/*skylarkImportLookupFunctionForInlining=*/ null));
skyFunctions.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction());
skyFunctions.put(SkyFunctions.LOCAL_REPOSITORY_LOOKUP, new LocalRepositoryLookupFunction());
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"));
}
@Test
public void testSimple() throws Exception {
assertGlobMatches("food", /* => */ "food");
}
@Test
public void testStartsWithStar() throws Exception {
assertGlobMatches("*oo", /* => */ "foo");
}
@Test
public void testStartsWithStarWithMiddleStar() throws Exception {
assertGlobMatches("*f*o", /* => */ "foo");
}
@Test
public void testSingleMatchEqual() throws Exception {
assertGlobsEqual("*oo", "*f*o"); // both produce "foo"
}
@Test
public void testEndsWithStar() throws Exception {
assertGlobMatches("foo*", /* => */ "foo", "food", "fool");
}
@Test
public void testEndsWithStarWithMiddleStar() throws Exception {
assertGlobMatches("f*oo*", /* => */ "foo", "food", "fool");
}
@Test
public void testMultipleMatchesEqual() throws Exception {
assertGlobsEqual("foo*", "f*oo*"); // both produce "foo", "food", "fool"
}
@Test
public void testMiddleStar() throws Exception {
assertGlobMatches("f*o", /* => */ "foo");
}
@Test
public void testTwoMiddleStars() throws Exception {
assertGlobMatches("f*o*o", /* => */ "foo");
}
@Test
public void testSingleStarPatternWithNamedChild() throws Exception {
assertGlobMatches("*/bar", /* => */ "foo/bar");
}
@Test
public void testDeepSubpackages() throws Exception {
assertGlobMatches("*/*/c", /* => */ "a1/b1/c");
}
@Test
public void testSingleStarPatternWithChildGlob() throws Exception {
assertGlobMatches(
"*/bar*", /* => */ "foo/bar", "foo/barnacle", "food/barnacle", "fool/barnacle");
}
@Test
public void testSingleStarAsChildGlob() throws Exception {
assertGlobMatches("foo/*/wiz", /* => */ "foo/bar/wiz", "foo/barnacle/wiz");
}
@Test
public void testNoAsteriskAndFilesDontExist() throws Exception {
// Note un-UNIX like semantics:
assertGlobMatches("ceci/n'est/pas/une/globbe" /* => nothing */);
}
@Test
public void testSingleAsteriskUnderNonexistentDirectory() throws Exception {
// Note un-UNIX like semantics:
assertGlobMatches("not-there/*" /* => nothing */);
}
@Test
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.
}
@Test
public void testGlobUnderFile() throws Exception {
assertGlobMatches("foo/bar/wiz/file/*" /* => nothing */);
}
@Test
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();
}
@Test
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");
}
@Test
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");
}
@Test
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/barnacle/wiz", "foo/barnacle", "foo");
}
@Test
public void testGlobDoesNotCrossPackageBoundaryUnderOtherPackagePath() throws Exception {
FileSystemUtils.createDirectoryAndParents(writableRoot.getRelative("pkg/foo/bar"));
FileSystemUtils.createEmptyFile(writableRoot.getRelative("pkg/foo/bar/BUILD"));
// "foo/bar" should not be in the results because foo/bar is detected as a separate package,
// even though it is under a different package path.
assertGlobMatches("foo/**", /* => */ "foo/barnacle/wiz", "foo/barnacle", "foo");
}
@Test
public void testGlobDoesNotCrossRepositoryBoundary() throws Exception {
FileSystemUtils.appendIsoLatin1(
root.getRelative("WORKSPACE"), "local_repository(name='local', path='pkg/foo')");
FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/WORKSPACE"));
FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/BUILD"));
// "foo/bar" should not be in the results because foo is a separate repository.
assertGlobMatches("f*/*", /* => */ "food/barnacle", "fool/barnacle");
}
@Test
public void testGlobDirectoryMatchDoesNotCrossRepositoryBoundary() throws Exception {
FileSystemUtils.appendIsoLatin1(
root.getRelative("WORKSPACE"), "local_repository(name='local', path='pkg/foo/bar')");
FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/bar/WORKSPACE"));
FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/bar/BUILD"));
// "foo/bar" should not be in the results because foo/bar is a separate repository.
assertGlobMatches("foo/*", /* => */ "foo/barnacle");
}
@Test
public void testStarStarDoesNotCrossRepositoryBoundary() throws Exception {
FileSystemUtils.appendIsoLatin1(
root.getRelative("WORKSPACE"), "local_repository(name='local', path='pkg/foo/bar')");
FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/bar/WORKSPACE"));
FileSystemUtils.createEmptyFile(pkgPath.getRelative("foo/bar/BUILD"));
// "foo/bar" should not be in the results because foo/bar is a separate repository.
assertGlobMatches("foo/**", /* => */ "foo/barnacle/wiz", "foo/barnacle", "foo");
}
@Test
public void testGlobDoesNotCrossRepositoryBoundaryUnderOtherPackagePath() throws Exception {
FileSystemUtils.appendIsoLatin1(
root.getRelative("WORKSPACE"),
"local_repository(name='local', path='"
+ writableRoot.getRelative("pkg/foo/bar").getPathString()
+ "')");
FileSystemUtils.createDirectoryAndParents(writableRoot.getRelative("pkg/foo/bar"));
FileSystemUtils.createEmptyFile(writableRoot.getRelative("pkg/foo/bar/WORKSPACE"));
FileSystemUtils.createEmptyFile(writableRoot.getRelative("pkg/foo/bar/BUILD"));
// "foo/bar" should not be in the results because foo/bar is detected as a separate package,
// even though it is under a different package path.
assertGlobMatches("foo/**", /* => */ "foo/barnacle/wiz", "foo/barnacle", "foo");
}
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 {
// The order requirement is not strictly necessary -- a change to GlobFunction semantics that
// changes the output order is fine, but we require that the order be the same here to detect
// potential non-determinism in output order, which would be bad.
// The current order in the case of "**" or "*" is roughly that of nestedset.Order.STABLE_ORDER,
// putting subdirectories before directories, but putting ordinary files after their parent
// directories.
assertThat(
Iterables.transform(
runGlob(excludeDirs, pattern).getMatches(), Functions.toStringFunction()))
.containsExactlyElementsIn(ImmutableList.copyOf(expecteds)).inOrder();
}
private void assertGlobsEqual(String pattern1, String pattern2) throws Exception {
GlobValue value1 = runGlob(false, pattern1);
GlobValue value2 = runGlob(false, pattern2);
new EqualsTester()
.addEqualityGroup(value1, value2)
.testEquals();
}
private GlobValue runGlob(boolean excludeDirs, String pattern) throws Exception {
SkyKey skyKey =
GlobValue.key(
PKG_ID, Root.fromPath(root), pattern, excludeDirs, PathFragment.EMPTY_FRAGMENT);
EvaluationResult<SkyValue> result =
driver.evaluate(ImmutableList.of(skyKey), EVALUATION_OPTIONS);
if (result.hasError()) {
throw result.getError().getException();
}
return (GlobValue) result.get(skyKey);
}
@Test
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.fromPath(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.fromPath(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.fromPath(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.fromPath(root), pkgPath.getRelative("foo/bar/wiz/file")))));
// This should have invalidated the glob result.
assertGlobMatches(pattern /* => nothing */);
}
}
@Test
public void testIllegalPatterns() throws Exception {
assertIllegalPattern("foo**bar");
assertIllegalPattern("?");
assertIllegalPattern("");
assertIllegalPattern(".");
assertIllegalPattern("/foo");
assertIllegalPattern("./foo");
assertIllegalPattern("foo/");
assertIllegalPattern("foo/./bar");
assertIllegalPattern("../foo/bar");
assertIllegalPattern("foo//bar");
}
@Test
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_ID, Root.fromPath(root), 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
*/
@Test
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);
}
@Test
public void testMatchesCallWithNoCache() {
assertThat(UnixGlob.matches("*a*b", "CaCb", null)).isTrue();
}
@Test
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(
"*", "..also.hidden", ".hidden", "BUILD", "a1", "a2", "foo", "food", "fool", "not.hidden");
assertGlobMatches("*.hidden", "not.hidden");
}
@Test
public void testDoubleStar() throws Exception {
assertGlobMatches(
"**",
"a1/b1/c",
"a1/b1",
"a1",
"a2",
"foo/bar/wiz",
"foo/bar/wiz/file",
"foo/bar",
"foo/barnacle/wiz",
"foo/barnacle",
"foo",
"food/barnacle/wiz",
"food/barnacle",
"food",
"fool/barnacle/wiz",
"fool/barnacle",
"fool",
"BUILD");
}
@Test
public void testDoubleStarExcludeDirs() throws Exception {
assertGlobWithoutDirsMatches("**", "foo/bar/wiz/file", "BUILD");
}
@Test
public void testDoubleDoubleStar() throws Exception {
assertGlobMatches(
"**/**",
"a1/b1/c",
"a1/b1",
"a1",
"a2",
"foo/bar/wiz",
"foo/bar/wiz/file",
"foo/bar",
"foo/barnacle/wiz",
"foo/barnacle",
"foo",
"food/barnacle/wiz",
"food/barnacle",
"food",
"fool/barnacle/wiz",
"fool/barnacle",
"fool",
"BUILD");
}
@Test
public void testDirectoryWithDoubleStar() throws Exception {
assertGlobMatches(
"foo/**",
"foo/bar/wiz",
"foo/bar/wiz/file",
"foo/bar",
"foo/barnacle/wiz",
"foo/barnacle",
"foo");
}
@Test
public void testDoubleStarPatternWithNamedChild() throws Exception {
assertGlobMatches("**/bar", "foo/bar");
}
@Test
public void testDoubleStarPatternWithChildGlob() throws Exception {
assertGlobMatches("**/ba*", "foo/bar", "foo/barnacle", "food/barnacle", "fool/barnacle");
}
@Test
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/wiz",
"foo/barnacle/baz/wiz",
"foo/barnacle/wiz/wiz");
}
@Test
public void testDoubleStarUnderNonexistentDirectory() throws Exception {
assertGlobMatches("not-there/**" /* => nothing */);
}
@Test
public void testDoubleStarUnderFile() throws Exception {
assertGlobMatches("foo/bar/wiz/file/**" /* => nothing */);
}
/** Regression test for b/13319874: Directory listing crash. */
@Test
public void testResilienceToFilesystemInconsistencies_DirectoryExistence() throws Exception {
// Our custom filesystem says "pkgPath/BUILD" exists but "pkgPath" does not exist.
fs.stubStat(pkgPath, null);
RootedPath pkgRootedPath = RootedPath.toRootedPath(Root.fromPath(root), pkgPath);
FileStateValue pkgDirFileStateValue = FileStateValue.create(pkgRootedPath, null);
FileValue pkgDirValue =
FileValue.value(
ImmutableList.of(pkgRootedPath),
pkgRootedPath,
pkgDirFileStateValue,
pkgRootedPath,
pkgDirFileStateValue);
differencer.inject(ImmutableMap.of(FileValue.key(pkgRootedPath), pkgDirValue));
String expectedMessage = "/root/workspace/pkg is no longer an existing directory";
SkyKey skyKey =
GlobValue.key(PKG_ID, Root.fromPath(root), "*/foo", false, PathFragment.EMPTY_FRAGMENT);
EvaluationResult<GlobValue> result =
driver.evaluate(ImmutableList.of(skyKey), EVALUATION_OPTIONS);
assertThat(result.hasError()).isTrue();
ErrorInfo errorInfo = result.getError(skyKey);
assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
assertThat(errorInfo.getException()).hasMessageThat().contains(expectedMessage);
}
@Test
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.fromPath(root), fooBarDir);
SkyValue fooBarDirListingValue =
DirectoryListingStateValue.create(
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_ID, Root.fromPath(root), "**/wiz", false, PathFragment.EMPTY_FRAGMENT);
EvaluationResult<GlobValue> result =
driver.evaluate(ImmutableList.of(skyKey), EVALUATION_OPTIONS);
assertThat(result.hasError()).isTrue();
ErrorInfo errorInfo = result.getError(skyKey);
assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
assertThat(errorInfo.getException()).hasMessageThat().contains(expectedMessage);
}
@Test
public void testResilienceToFilesystemInconsistencies_SymlinkType() throws Exception {
RootedPath wizRootedPath =
RootedPath.toRootedPath(Root.fromPath(root), pkgPath.getRelative("foo/bar/wiz"));
RootedPath fileRootedPath =
RootedPath.toRootedPath(Root.fromPath(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.create(
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_ID, Root.fromPath(root), "foo/bar/wiz/*", false, PathFragment.EMPTY_FRAGMENT);
EvaluationResult<GlobValue> result =
driver.evaluate(ImmutableList.of(skyKey), EVALUATION_OPTIONS);
assertThat(result.hasError()).isTrue();
ErrorInfo errorInfo = result.getError(skyKey);
assertThat(errorInfo.getException()).isInstanceOf(InconsistentFilesystemException.class);
assertThat(errorInfo.getException()).hasMessageThat().contains(expectedMessage);
}
@Test
public void testSymlinks() throws Exception {
FileSystemUtils.createDirectoryAndParents(pkgPath.getRelative("symlinks"));
FileSystemUtils.ensureSymbolicLink(pkgPath.getRelative("symlinks/dangling.txt"), "nope");
FileSystemUtils.createEmptyFile(pkgPath.getRelative("symlinks/yup"));
FileSystemUtils.ensureSymbolicLink(pkgPath.getRelative("symlinks/existing.txt"), "yup");
assertGlobMatches("symlinks/*.txt", "symlinks/existing.txt");
}
private static final 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 statIfFound(Path path, boolean followSymlinks) throws IOException {
if (stubbedStats.containsKey(path)) {
return stubbedStats.get(path);
}
return super.statIfFound(path, followSymlinks);
}
}
}