Automated rollback of commit 16465d7613348e39e0bcdb22697cf67d257a3d91.
*** Reason for rollback ***
We need to reconsider this.
https://devblogs.microsoft.com/commandline/per-directory-case-sensitivity-and-wsl/ says case-sensitivity can be set per-directory. Requiring correct casing for every directory would make Bazel behave the same on Linux and Windows, and work with WSL-created paths.
*** Original change description ***
glob() now supports case-insensitive mode
The behavior is triggered by
FileSystem.isGlobCaseSensitive() returning false.
None of the production FileSystem implementaitions
return false yet, only some test implementations.
Motivation is to support case-insensitive glob()
on Windows.
See https://github.com/bazelbuild/bazel/issues/8705 and https://github.com/bazelbuild/bazel/issues/8759.
Next we need to add an incompatible flag that
enables this behavior, and add a relevant bit to
the WindowsFileSystem. See https://github.com/bazelbuild/bazel/issues/8767
We must also warn the user somehow if enabling
this feature would change the result of some
globs. A potential approach would be to glob
case-sensitively and case-insensitively at the
same time and warn the user if the results are
different.
PiperOrigin-RevId: 256556254
diff --git a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
index db58cfc..8d1bb74 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
@@ -255,13 +255,7 @@
}
results.addAll(items);
}
-
- // TODO(laszlocsomor): set `caseSensitive` from the value of
- // `--incompatible_windows_case_insensitive_glob` or from FileSystem.isGlobCaseSensitive()
- // See https://github.com/bazelbuild/bazel/issues/8767
- final boolean caseSensitive = true;
-
- UnixGlob.removeExcludes(results, excludes, caseSensitive);
+ UnixGlob.removeExcludes(results, excludes);
if (!allowEmpty && results.isEmpty()) {
throw new BadGlobException(
"all files in the glob have been excluded, but allow_empty is set to False.");
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
index e40c76f..cabb54f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunction.java
@@ -43,10 +43,6 @@
*/
public final class GlobFunction implements SkyFunction {
- // TODO(laszlocsomor): we might need to create another regexPatternCache for case-insensitive
- // glob() mode to avoid reading wrong cache results when the user changes the
- // --incompatible_windows_case_ignoring_glob flag value between builds.
- // See https://github.com/bazelbuild/bazel/issues/8767.
private final ConcurrentHashMap<String, Pattern> regexPatternCache = new ConcurrentHashMap<>();
private final boolean alwaysUseDirListing;
@@ -156,11 +152,6 @@
}
}
- // TODO(laszlocsomor): set `caseSensitive` from the value of
- // `--incompatible_windows_case_insensitive_glob` or from FileSystem.isGlobCaseSensitive()
- // See https://github.com/bazelbuild/bazel/issues/8767
- final boolean caseSensitive = true;
-
// Now that we have the directory listing, we do three passes over it so as to maximize
// skyframe batching:
// (1) Process every dirent, keeping track of values we need to request if the dirent cannot
@@ -178,7 +169,7 @@
for (Dirent dirent : listingValue.getDirents()) {
Dirent.Type direntType = dirent.getType();
String fileName = dirent.getName();
- if (!UnixGlob.matches(patternHead, fileName, regexPatternCache, caseSensitive)) {
+ if (!UnixGlob.matches(patternHead, fileName, regexPatternCache)) {
continue;
}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index cfbe006..23a6c77 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -1011,13 +1011,7 @@
if (legacyIncludesToken != null) {
matches.addAll(delegate.fetch(legacyIncludesToken));
}
-
- // TODO(laszlocsomor): set `caseSensitive` from the value of
- // `--incompatible_windows_case_insensitive_glob` or from FileSystem.isGlobCaseSensitive()
- // See https://github.com/bazelbuild/bazel/issues/8767
- final boolean caseSensitive = true;
-
- UnixGlob.removeExcludes(matches, excludes, caseSensitive);
+ UnixGlob.removeExcludes(matches, excludes);
List<String> result = new ArrayList<>(matches);
// Skyframe glob results are unsorted. And we used a LegacyGlobber that doesn't sort.
// Therefore, we want to unconditionally sort here.
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
index 7f6b6e0..abaa6b1 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -138,22 +138,6 @@
public abstract boolean isFilePathCaseSensitive();
/**
- * Returns true if glob() is case-sensitive.
- *
- * <p>When glob() is case-sensitive, it will only match (or exclude) file "Foo" if the include (or
- * exclude) pattern uses the same upper-case and lower-case letters.
- *
- * <p>When glob() is case-insensitive, it will match (or exclude) the file "Foo" even if the
- * include (or exclude) pattern uses a different casing such as "foO".
- */
- // TODO(laszlocsomor): After `--incompatible_windows_case_insensitive_glob` is flipped to true,
- // remove this method and all references to it and replace call sites with
- // isFilePathCaseSensitive(). See https://github.com/bazelbuild/bazel/issues/8767
- public boolean isGlobCaseSensitive() {
- return true;
- }
-
- /**
* Returns the type of the file system path belongs to.
*
* <p>The string returned is obtained directly from the operating system, so
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
index a6355ef..d9175a2 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/UnixGlob.java
@@ -15,7 +15,6 @@
package com.google.devtools.build.lib.vfs;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Ascii;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
@@ -156,9 +155,9 @@
return null;
}
- /** Calls {@link #matches(String, String, Map) matches(pattern, str, null, boolean)} */
- public static boolean matches(String pattern, String str, boolean caseSensitive) {
- return matches(pattern, str, null, caseSensitive);
+ /** Calls {@link #matches(String, String, Map) matches(pattern, str, null)} */
+ public static boolean matches(String pattern, String str) {
+ return matches(pattern, str, null);
}
/**
@@ -170,8 +169,7 @@
* @param patternCache a cache from patterns to compiled Pattern objects, or {@code null} to skip
* caching
*/
- public static boolean matches(
- String pattern, String str, Map<String, Pattern> patternCache, boolean caseSensitive) {
+ public static boolean matches(String pattern, String str, Map<String, Pattern> patternCache) {
if (pattern.length() == 0 || str.length() == 0) {
return false;
}
@@ -193,30 +191,30 @@
// Common case: *.xyz
if (pattern.charAt(0) == '*' && pattern.lastIndexOf('*') == 0) {
- return endsWithCase(str, pattern.substring(1), caseSensitive);
+ return str.endsWith(pattern.substring(1));
}
// Common case: xyz*
int lastIndex = pattern.length() - 1;
// The first clause of this if statement is unnecessary, but is an
// optimization--charAt runs faster than indexOf.
if (pattern.charAt(lastIndex) == '*' && pattern.indexOf('*') == lastIndex) {
- return startsWithCase(str, pattern.substring(0, lastIndex), caseSensitive);
+ return str.startsWith(pattern.substring(0, lastIndex));
}
Pattern regex =
patternCache == null
- ? makePatternFromWildcard(pattern, caseSensitive)
- : patternCache.computeIfAbsent(pattern, p -> makePatternFromWildcard(p, caseSensitive));
+ ? makePatternFromWildcard(pattern)
+ : patternCache.computeIfAbsent(pattern, p -> makePatternFromWildcard(p));
return regex.matcher(str).matches();
}
/**
- * Returns a regular expression implementing a matcher for "pattern", in which "*" and "?" are
- * wildcards.
+ * Returns a regular expression implementing a matcher for "pattern", in which
+ * "*" and "?" are wildcards.
*
* <p>e.g. "foo*bar?.java" -> "foo.*bar.\\.java"
*/
- private static Pattern makePatternFromWildcard(String pattern, boolean caseSensitive) {
+ private static Pattern makePatternFromWildcard(String pattern) {
StringBuilder regexp = new StringBuilder();
for (int i = 0, len = pattern.length(); i < len; i++) {
char c = pattern.charAt(i);
@@ -249,15 +247,7 @@
regexp.append(c);
break;
default:
- if (caseSensitive || !isAlphaAscii(c)) {
- regexp.append(c);
- } else {
- regexp
- .append('[')
- .append(Ascii.toUpperCase(c))
- .append(Ascii.toLowerCase(c))
- .append(']');
- }
+ regexp.append(c);
break;
}
}
@@ -568,6 +558,7 @@
if (baseStat == null || patterns.isEmpty()) {
return Futures.immediateFuture(Collections.<Path>emptyList());
}
+
List<String[]> splitPatterns = checkAndSplitPatterns(patterns);
// We do a dumb loop, even though it will likely duplicate logical work (note that the
@@ -576,7 +567,6 @@
// glob [*/*.java, sub/*.java, */*.txt]).
pendingOps.incrementAndGet();
try {
- final boolean caseSensitive = base.getFileSystem().isGlobCaseSensitive();
for (String[] splitPattern : splitPatterns) {
int numRecursivePatterns = 0;
for (String pattern : splitPattern) {
@@ -584,12 +574,9 @@
++numRecursivePatterns;
}
}
- GlobTaskContext context =
- numRecursivePatterns > 1
- ? new RecursiveGlobTaskContext(
- splitPattern, excludeDirectories, caseSensitive, dirPred, syscalls)
- : new GlobTaskContext(
- splitPattern, excludeDirectories, caseSensitive, dirPred, syscalls);
+ GlobTaskContext context = numRecursivePatterns > 1
+ ? new RecursiveGlobTaskContext(splitPattern, excludeDirectories, dirPred, syscalls)
+ : new GlobTaskContext(splitPattern, excludeDirectories, dirPred, syscalls);
context.queueGlob(base, baseStat.isDirectory(), 0);
}
} finally {
@@ -698,19 +685,16 @@
private class GlobTaskContext {
private final String[] patternParts;
private final boolean excludeDirectories;
- private final boolean caseSensitive;
private final Predicate<Path> dirPred;
private final FilesystemCalls syscalls;
GlobTaskContext(
String[] patternParts,
boolean excludeDirectories,
- boolean caseSensitive,
Predicate<Path> dirPred,
FilesystemCalls syscalls) {
this.patternParts = patternParts;
this.excludeDirectories = excludeDirectories;
- this.caseSensitive = caseSensitive;
this.dirPred = dirPred;
this.syscalls = syscalls;
}
@@ -760,10 +744,9 @@
private RecursiveGlobTaskContext(
String[] patternParts,
boolean excludeDirectories,
- boolean caseSensitive,
Predicate<Path> dirPred,
FilesystemCalls syscalls) {
- super(patternParts, excludeDirectories, caseSensitive, dirPred, syscalls);
+ super(patternParts, excludeDirectories, dirPred, syscalls);
}
@Override
@@ -837,7 +820,7 @@
// The file is a special file (fifo, etc.). No need to even match against the pattern.
continue;
}
- if (matches(pattern, dent.getName(), cache, context.caseSensitive)) {
+ if (matches(pattern, dent.getName(), cache)) {
Path child = base.getChild(dent.getName());
if (childType == Dirent.Type.SYMLINK) {
@@ -885,17 +868,12 @@
* Filters out exclude patterns from a Set of paths. Common cases such as wildcard-free patterns
* or suffix patterns are special-cased to make this function efficient.
*/
- public static void removeExcludes(
- Set<String> paths, Collection<String> excludes, boolean caseSensitive) {
+ public static void removeExcludes(Set<String> paths, Collection<String> excludes) {
ArrayList<String> complexPatterns = new ArrayList<>(excludes.size());
Map<String, List<String>> starstarSlashStarHeadTailPairs = new HashMap<>();
for (String exclude : excludes) {
if (isWildcardFree(exclude)) {
- if (caseSensitive) {
- paths.remove(exclude);
- } else {
- paths.removeIf(p -> Ascii.equalsIgnoreCase(p, exclude));
- }
+ paths.remove(exclude);
continue;
}
int patternPos = exclude.indexOf("**/*");
@@ -912,9 +890,9 @@
for (Map.Entry<String, List<String>> headTailPair : starstarSlashStarHeadTailPairs.entrySet()) {
paths.removeIf(
path -> {
- if (startsWithCase(path, headTailPair.getKey(), caseSensitive)) {
+ if (path.startsWith(headTailPair.getKey())) {
for (String tail : headTailPair.getValue()) {
- if (endsWithCase(path, tail, caseSensitive)) {
+ if (path.endsWith(tail)) {
return true;
}
}
@@ -931,7 +909,7 @@
path -> {
String[] segments = Iterables.toArray(Splitter.on('/').split(path), String.class);
for (String[] splitPattern : splitPatterns) {
- if (matchesPattern(splitPattern, segments, 0, 0, patternCache, caseSensitive)) {
+ if (matchesPattern(splitPattern, segments, 0, 0, patternCache)) {
return true;
}
}
@@ -941,25 +919,19 @@
/** Returns true if {@code pattern} matches {@code path} starting from the given segments. */
private static boolean matchesPattern(
- String[] pattern,
- String[] path,
- int i,
- int j,
- Map<String, Pattern> patternCache,
- boolean caseSensitive) {
+ String[] pattern, String[] path, int i, int j, Map<String, Pattern> patternCache) {
if (i == pattern.length) {
return j == path.length;
}
if (pattern[i].equals("**")) {
- return matchesPattern(pattern, path, i + 1, j, patternCache, caseSensitive)
- || (j < path.length
- && matchesPattern(pattern, path, i, j + 1, patternCache, caseSensitive));
+ return matchesPattern(pattern, path, i + 1, j, patternCache)
+ || (j < path.length && matchesPattern(pattern, path, i, j + 1, patternCache));
}
if (j == path.length) {
return false;
}
- if (matches(pattern[i], path[j], patternCache, caseSensitive)) {
- return matchesPattern(pattern, path, i + 1, j + 1, patternCache, caseSensitive);
+ if (matches(pattern[i], path[j], patternCache)) {
+ return matchesPattern(pattern, path, i + 1, j + 1, patternCache);
}
return false;
}
@@ -967,28 +939,4 @@
private static boolean isWildcardFree(String pattern) {
return !pattern.contains("*") && !pattern.contains("?");
}
-
- @VisibleForTesting
- static boolean startsWithCase(String s, String p, boolean caseSensitive) {
- if (caseSensitive) {
- return s.startsWith(p);
- } else {
- return s.length() >= p.length() && s.regionMatches(true, 0, p, 0, p.length());
- }
- }
-
- @VisibleForTesting
- static boolean endsWithCase(String s, String p, boolean caseSensitive) {
- if (caseSensitive) {
- return s.endsWith(p);
- } else {
- return s.length() >= p.length()
- && s.regionMatches(true, s.length() - p.length(), p, 0, p.length());
- }
- }
-
- @VisibleForTesting
- static boolean isAlphaAscii(char c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
- }
}
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
index abb7c63..5b3669b 100644
--- a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
+++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
@@ -106,13 +106,6 @@
}
@Override
- public boolean isGlobCaseSensitive() {
- // TODO(laszlocsomor): return the opposite of `--incompatible_windows_case_insensitive_glob`
- // here. See https://github.com/bazelbuild/bazel/issues/8767
- return true;
- }
-
- @Override
protected boolean fileIsSymbolicLink(File file) {
try {
if (isSymlinkOrJunction(file)) {
diff --git a/src/test/java/com/google/devtools/build/lib/BUILD b/src/test/java/com/google/devtools/build/lib/BUILD
index 8199ba1..fbf4d42 100644
--- a/src/test/java/com/google/devtools/build/lib/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/BUILD
@@ -9,7 +9,6 @@
CROSS_PLATFORM_WINDOWS_TESTS = [
"util/DependencySetWindowsTest.java",
"vfs/PathFragmentWindowsTest.java",
- "vfs/WindowsGlobTest.java",
"vfs/WindowsPathTest.java",
]
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
index e30cb90..3d85f63 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/GlobFunctionTest.java
@@ -544,7 +544,7 @@
@Test
public void testMatchesCallWithNoCache() {
- assertThat(UnixGlob.matches("*a*b", "CaCb", null, true)).isTrue();
+ assertThat(UnixGlob.matches("*a*b", "CaCb", null)).isTrue();
}
@Test
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/GlobTest.java b/src/test/java/com/google/devtools/build/lib/vfs/GlobTest.java
index efa15f5..7eb2a66 100644
--- a/src/test/java/com/google/devtools/build/lib/vfs/GlobTest.java
+++ b/src/test/java/com/google/devtools/build/lib/vfs/GlobTest.java
@@ -285,7 +285,7 @@
@Test
public void testMatchesCallWithNoCache() {
- assertThat(UnixGlob.matches("*a*b", "CaCb", null, true)).isTrue();
+ assertThat(UnixGlob.matches("*a*b", "CaCb", null)).isTrue();
}
@Test
@@ -297,11 +297,11 @@
public void testMatcherMethodRecursiveBelowDir() throws Exception {
FileSystemUtils.createEmptyFile(tmpPath.getRelative("foo/file"));
String pattern = "foo/**/*";
- assertThat(UnixGlob.matches(pattern, "foo/bar", true)).isTrue();
- assertThat(UnixGlob.matches(pattern, "foo/bar/baz", true)).isTrue();
- assertThat(UnixGlob.matches(pattern, "foo", true)).isFalse();
- assertThat(UnixGlob.matches(pattern, "foob", true)).isFalse();
- assertThat(UnixGlob.matches("**/foo", "foo", true)).isTrue();
+ assertThat(UnixGlob.matches(pattern, "foo/bar")).isTrue();
+ assertThat(UnixGlob.matches(pattern, "foo/bar/baz")).isTrue();
+ assertThat(UnixGlob.matches(pattern, "foo")).isFalse();
+ assertThat(UnixGlob.matches(pattern, "foob")).isFalse();
+ assertThat(UnixGlob.matches("**/foo", "foo")).isTrue();
}
@Test
@@ -440,7 +440,7 @@
private Collection<String> removeExcludes(ImmutableList<String> paths, String... excludes) {
HashSet<String> pathSet = new HashSet<>(paths);
- UnixGlob.removeExcludes(pathSet, ImmutableList.copyOf(excludes), true);
+ UnixGlob.removeExcludes(pathSet, ImmutableList.copyOf(excludes));
return pathSet;
}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/WindowsGlobTest.java b/src/test/java/com/google/devtools/build/lib/vfs/WindowsGlobTest.java
deleted file mode 100644
index 1303b4b..0000000
--- a/src/test/java/com/google/devtools/build/lib/vfs/WindowsGlobTest.java
+++ /dev/null
@@ -1,392 +0,0 @@
-// Copyright 2019 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.vfs;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
-import com.google.devtools.build.lib.clock.BlazeClock;
-import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.concurrent.atomic.AtomicReference;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Tests glob() on Windows (when glob is case-insensitive). */
-@RunWith(JUnit4.class)
-public class WindowsGlobTest {
-
- private static void assertMatches(
- UnixGlob.FilesystemCalls fsCalls, Path root, String pattern, String... expecteds)
- throws IOException {
- AtomicReference<UnixGlob.FilesystemCalls> sysCalls = new AtomicReference<>(fsCalls);
- assertThat(
- Iterables.transform(
- new UnixGlob.Builder(root)
- .addPattern(pattern)
- .setFilesystemCalls(sysCalls)
- .setExcludeDirectories(true)
- .glob(),
- // Convert Paths to Strings, otherwise they'd be compared with the host system's
- // Path
- // comparison semantics.
- a -> a.relativeTo(root).toString()))
- .containsExactlyElementsIn(expecteds);
- }
-
- private static void assertExcludes(
- Collection<String> unfiltered,
- String exclusionPattern,
- boolean caseSensitive,
- Collection<String> expected) {
- Set<String> matched = new HashSet<>(unfiltered);
- UnixGlob.removeExcludes(matched, ImmutableList.of(exclusionPattern), caseSensitive);
- assertThat(matched).containsExactlyElementsIn(expected);
- }
-
- @Test
- public void testMatches() throws Exception {
- assertThat(UnixGlob.matches("Foo/**", "Foo/Bar/a.txt", null, true)).isTrue();
- assertThat(UnixGlob.matches("Foo/**", "Foo/Bar/a.txt", null, false)).isTrue();
-
- assertThat(UnixGlob.matches("foo/**", "Foo/Bar/a.txt", null, true)).isFalse();
- assertThat(UnixGlob.matches("foo/**", "Foo/Bar/a.txt", null, false)).isTrue();
-
- assertThat(UnixGlob.matches("F*o*o/**", "Foo/Bar/a.txt", null, true)).isTrue();
- assertThat(UnixGlob.matches("F*o*o/**", "Foo/Bar/a.txt", null, false)).isTrue();
-
- assertThat(UnixGlob.matches("f*o*o/**", "Foo/Bar/a.txt", null, true)).isFalse();
- assertThat(UnixGlob.matches("f*o*o/**", "Foo/Bar/a.txt", null, false)).isTrue();
-
- assertThat(UnixGlob.matches("Foo/**", "Foo/Bar/a.txt", null, true)).isTrue();
- assertThat(UnixGlob.matches("Foo/**", "Foo/Bar/a.txt", null, false)).isTrue();
- }
-
- @Test
- public void testExcludes() throws Exception {
- assertExcludes(
- Arrays.asList("Foo/Bar/a.txt", "Foo/Bar/b.dat"),
- "Foo/**/*.dat",
- true,
- Arrays.asList("Foo/Bar/a.txt"));
- assertExcludes(
- Arrays.asList("Foo/Bar/a.txt", "Foo/Bar/b.dat"),
- "Foo/**/*.dat",
- false,
- Arrays.asList("Foo/Bar/a.txt"));
-
- assertExcludes(
- Arrays.asList("Foo/Bar/a.txt", "Foo/Bar/b.dat"),
- "foo/**/*.dat",
- true,
- Arrays.asList("Foo/Bar/a.txt", "Foo/Bar/b.dat"));
- assertExcludes(
- Arrays.asList("Foo/Bar/a.txt", "Foo/Bar/b.dat"),
- "foo/**/*.dat",
- false,
- Arrays.asList("Foo/Bar/a.txt"));
- }
-
- private enum MockStat implements FileStatus {
- FILE(true, false),
- DIR(false, true),
- UNKNOWN(false, false);
-
- private final boolean isFile;
- private final boolean isDir;
-
- private MockStat(boolean isFile, boolean isDir) {
- this.isFile = isFile;
- this.isDir = isDir;
- }
-
- @Override
- public boolean isFile() {
- return isFile;
- }
-
- @Override
- public boolean isDirectory() {
- return isDir;
- }
-
- @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 {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getLastChangeTime() throws IOException {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public long getNodeId() throws IOException {
- throw new UnsupportedOperationException();
- }
- }
-
- private static FileSystem mockFs(boolean caseSensitive) throws IOException {
- FileSystem fs =
- new InMemoryFileSystem(BlazeClock.instance()) {
- @Override
- public boolean isFilePathCaseSensitive() {
- return caseSensitive;
- }
-
- @Override
- public boolean isGlobCaseSensitive() {
- return isFilePathCaseSensitive();
- }
- };
- fs.getPath("/globtmp/Foo/Bar").createDirectoryAndParents();
- FileSystemUtils.writeContentAsLatin1(fs.getPath("/globtmp/Foo/Bar/a.txt"), "foo");
- FileSystemUtils.writeContentAsLatin1(fs.getPath("/globtmp/Foo/Bar/b.dat"), "bar");
- return fs;
- }
-
- private static Map<String, Collection<Dirent>> mockDirents(Path root) {
- // Map keys must be Strings not Paths, lest they follow the host OS' case-sensitivity policy.
- Map<String, Collection<Dirent>> d =
- root.getFileSystem().isGlobCaseSensitive()
- ? new HashMap<>()
- : new TreeMap<>((String x, String y) -> x.compareToIgnoreCase(y));
- d.put(
- root.getParentDirectory().toString(),
- ImmutableList.of(new Dirent(root.getBaseName(), Dirent.Type.DIRECTORY)));
- d.put(root.toString(), ImmutableList.of(new Dirent("Foo", Dirent.Type.DIRECTORY)));
- d.put(
- root.getRelative("Foo").toString(),
- ImmutableList.of(new Dirent("Bar", Dirent.Type.DIRECTORY)));
- d.put(
- root.getRelative("Foo/Bar").toString(),
- ImmutableList.of(
- new Dirent("a.txt", Dirent.Type.FILE), new Dirent("b.dat", Dirent.Type.FILE)));
- return d;
- }
-
- private static Map<String, FileStatus> mockStats(Path root) {
- // Map keys must be Strings not Paths, lest they follow the host OS' case-sensitivity policy.
- Map<String, FileStatus> d =
- root.getFileSystem().isGlobCaseSensitive()
- ? new HashMap<>()
- : new TreeMap<>((String x, String y) -> x.compareToIgnoreCase(y));
- d.put(root.getParentDirectory().toString(), MockStat.DIR);
- d.put(root.toString(), MockStat.DIR);
- d.put(root.getRelative("Foo").toString(), MockStat.DIR);
- d.put(root.getRelative("Foo/Bar").toString(), MockStat.DIR);
- d.put(root.getRelative("Foo/Bar/a.txt").toString(), MockStat.FILE);
- d.put(root.getRelative("Foo/Bar/b.dat").toString(), MockStat.FILE);
- return d;
- }
-
- private static UnixGlob.FilesystemCalls mockFsCalls(Path root) {
- return new UnixGlob.FilesystemCalls() {
- // These maps use case-sensitive or case-insensitive key comparison depending on
- // root.getFileSystem().isGlobCaseSensitive()
- private final Map<String, Collection<Dirent>> dirents = mockDirents(root);
- private final Map<String, FileStatus> stats = mockStats(root);
-
- @Override
- public Collection<Dirent> readdir(Path path) throws IOException {
- String p = path.toString();
- if (dirents.containsKey(p)) {
- return dirents.get(p);
- }
- throw new IOException(p);
- }
-
- @Override
- public FileStatus statIfFound(Path path, Symlinks symlinks) throws IOException {
- String p = path.toString();
- if (stats.containsKey(p)) {
- return stats.get(p);
- }
- return MockStat.UNKNOWN;
- }
-
- @Override
- public Dirent.Type getType(Path path, Symlinks symlinks) throws IOException {
- String p = path.toString();
- if (dirents.containsKey(p)) {
- for (Dirent d : dirents.get(p)) {
- if (d.getName().equals(path.getBaseName())) {
- return d.getType();
- }
- }
- }
- throw new IOException(p);
- }
- };
- }
-
- @Test
- public void testFoo() throws Exception {
- FileSystem unixFs = mockFs(/* caseSensitive */ true);
- FileSystem winFs = mockFs(/* caseSensitive */ false);
-
- Path unixRoot = unixFs.getPath("/globtmp");
- Path winRoot = winFs.getPath("/globtmp");
-
- Path unixRoot2 = unixFs.getPath("/globTMP");
- Path winRoot2 = winFs.getPath("/globTMP");
-
- UnixGlob.FilesystemCalls unixFsCalls = mockFsCalls(unixRoot);
- UnixGlob.FilesystemCalls winFsCalls = mockFsCalls(winRoot);
-
- // Try a simple, non-recursive glob that matches no files. (Directories are exluded.)
- assertMatches(unixFsCalls, unixRoot, "Foo/*");
- assertMatches(winFsCalls, winRoot, "Foo/*");
-
- // Try a simple, non-recursive glob that should match some files.
- assertMatches(unixFsCalls, unixRoot, "Foo/*/*", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
- assertMatches(winFsCalls, winRoot, "Foo/*/*", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
-
- // Try a recursive glob.
- assertMatches(unixFsCalls, unixRoot, "Foo/**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
- assertMatches(winFsCalls, winRoot, "Foo/**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
-
- // Try a recursive glob, but use incorrect casing in the pattern.
- // The case-insensitive glob should match, but the results retain the casing of the pattern
- // ("foO") because the glob logic checks its existence with 'statIfFound', and uses the name
- // from the pattern and not from the filesystem.
- // The **-matched parts use the actual casing ("Bar") because the glob does a 'readdir' to get
- // these.
- assertMatches(unixFsCalls, unixRoot, "foO/**");
- assertMatches(winFsCalls, winRoot, "foO/**", "foO/Bar/a.txt", "foO/Bar/b.dat");
-
- // Try the same with another path component in the glob pattern. The casing of that pattern
- // should be retained in the results.
- assertMatches(unixFsCalls, unixRoot, "foO/baR/*");
- assertMatches(winFsCalls, winRoot, "foO/baR/*", "foO/baR/a.txt", "foO/baR/b.dat");
-
- // Even if the root's casing is incorrect, the case-insensitive glob should match it.
- assertMatches(unixFsCalls, unixRoot2, "**");
- assertMatches(winFsCalls, winRoot2, "**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
-
- // Try again the "wrong" root with a "wrong" first component. The result should retain the
- // casing.
- assertMatches(unixFsCalls, unixRoot2, "foO/**");
- assertMatches(winFsCalls, winRoot2, "foO/**", "foO/Bar/a.txt", "foO/Bar/b.dat");
-
- // Try the same with more "wrong" path components in the pattern. The results should retain all
- // the casing.
- assertMatches(unixFsCalls, unixRoot2, "foO/baR/A.TXT");
- assertMatches(winFsCalls, winRoot2, "foO/baR/A.TXT", "foO/baR/A.TXT");
-
- // Try a so-called "complex" pattern in the directory name. The glob logic creates a regex and
- // matches it against the result of a 'readdir', so the result retains the filesystem casing.
- assertMatches(unixFsCalls, unixRoot, "Foo/*R/*");
- assertMatches(winFsCalls, winRoot, "Foo/*R/*", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
-
- // Try the same for a file name pattern. Again, the filesystem casing is used because the
- // matching is done with a regex.
- assertMatches(unixFsCalls, unixRoot, "Foo/Bar/*.TXT");
- assertMatches(winFsCalls, winRoot, "Foo/Bar/*.TXT", "Foo/Bar/a.txt");
-
- // Try the same with a recursive pattern.
- assertMatches(unixFsCalls, unixRoot, "F*o*o/**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
- assertMatches(winFsCalls, winRoot, "F*o*o/**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
-
- // Try the same with wrong casing.
- assertMatches(unixFsCalls, unixRoot, "f*o*O/**");
- assertMatches(winFsCalls, winRoot, "f*o*O/**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
-
- // A "complex" first pattern with the right casing, but wrongly-cased root.
- assertMatches(unixFsCalls, unixRoot2, "F*o*o/**");
- assertMatches(winFsCalls, winRoot2, "F*o*o/**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
-
- // A "complex" first pattern with the wrong casing and wrongly-cased root.
- assertMatches(unixFsCalls, unixRoot2, "f*o*O/**");
- assertMatches(winFsCalls, winRoot2, "f*o*O/**", "Foo/Bar/a.txt", "Foo/Bar/b.dat");
- }
-
- @Test
- public void testStartsWithCase() {
- assertThat(UnixGlob.startsWithCase("", "", true)).isTrue();
- assertThat(UnixGlob.startsWithCase("", "", false)).isTrue();
-
- assertThat(UnixGlob.startsWithCase("Foo", "", true)).isTrue();
- assertThat(UnixGlob.startsWithCase("Foo", "", false)).isTrue();
-
- assertThat(UnixGlob.startsWithCase("", "Foo", true)).isFalse();
- assertThat(UnixGlob.startsWithCase("", "Foo", false)).isFalse();
-
- assertThat(UnixGlob.startsWithCase("Fo", "Foo", true)).isFalse();
- assertThat(UnixGlob.startsWithCase("Fo", "Foo", false)).isFalse();
-
- assertThat(UnixGlob.startsWithCase("Foo", "Foo", true)).isTrue();
- assertThat(UnixGlob.startsWithCase("Foo", "Foo", false)).isTrue();
-
- assertThat(UnixGlob.startsWithCase("Foox", "Foo", true)).isTrue();
- assertThat(UnixGlob.startsWithCase("Foox", "Foo", false)).isTrue();
-
- assertThat(UnixGlob.startsWithCase("xFoo", "Foo", true)).isFalse();
- assertThat(UnixGlob.startsWithCase("xFoo", "Foo", false)).isFalse();
-
- assertThat(UnixGlob.startsWithCase("Foox", "foO", true)).isFalse();
- assertThat(UnixGlob.startsWithCase("Foox", "foO", false)).isTrue();
- }
-
- @Test
- public void testEndsWithCase() {
- assertThat(UnixGlob.endsWithCase("", "", true)).isTrue();
- assertThat(UnixGlob.endsWithCase("", "", false)).isTrue();
-
- assertThat(UnixGlob.endsWithCase("Foo", "", true)).isTrue();
- assertThat(UnixGlob.endsWithCase("Foo", "", false)).isTrue();
-
- assertThat(UnixGlob.endsWithCase("", "Foo", true)).isFalse();
- assertThat(UnixGlob.endsWithCase("", "Foo", false)).isFalse();
-
- assertThat(UnixGlob.endsWithCase("Fo", "Foo", true)).isFalse();
- assertThat(UnixGlob.endsWithCase("Fo", "Foo", false)).isFalse();
-
- assertThat(UnixGlob.endsWithCase("Foo", "Foo", true)).isTrue();
- assertThat(UnixGlob.endsWithCase("Foo", "Foo", false)).isTrue();
-
- assertThat(UnixGlob.endsWithCase("Foox", "Foo", true)).isFalse();
- assertThat(UnixGlob.endsWithCase("Foox", "Foo", false)).isFalse();
-
- assertThat(UnixGlob.endsWithCase("xFoo", "Foo", true)).isTrue();
- assertThat(UnixGlob.endsWithCase("xFoo", "Foo", false)).isTrue();
-
- assertThat(UnixGlob.endsWithCase("xFoo", "foO", true)).isFalse();
- assertThat(UnixGlob.endsWithCase("xFoo", "foO", false)).isTrue();
- }
-}