aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/test/java/com/google/devtools/build/lib/vfs
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/com/google/devtools/build/lib/vfs')
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemConcurrencyTest.java97
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java1356
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java878
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/FileSystemsTest.java48
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/GlobTest.java417
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java40
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/ModifiedFileSetTest.java54
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java481
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java218
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathTest.java312
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java98
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/RecursiveGlobTest.java227
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/RootedPathTest.java56
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java806
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java717
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java330
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java63
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/UnixPathEqualityTest.java118
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/UnixPathGetParentTest.java87
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java279
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/ZipFileSystemTest.java233
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfoTest.java73
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java414
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/sample_with_dirs.zipbin0 -> 1247 bytes
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/sample_without_dirs.zipbin0 -> 737 bytes
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/util/FileSystems.java93
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java158
27 files changed, 7653 insertions, 0 deletions
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemConcurrencyTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemConcurrencyTest.java
new file mode 100644
index 0000000000..c90de18082
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemConcurrencyTest.java
@@ -0,0 +1,97 @@
+// Copyright 2014 Google Inc. 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 com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.vfs.util.FileSystems;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * (Slow) tests of FileSystem under concurrency.
+ *
+ * These tests are nondeterministic but provide good coverage nonetheless.
+ */
+@RunWith(JUnit4.class)
+public class FileSystemConcurrencyTest {
+
+ Path workingDir;
+
+ @Before
+ public void setUp() throws Exception {
+ FileSystem testFS = FileSystems.initDefaultAsNative();
+
+ // Resolve symbolic links in the temp dir:
+ workingDir = testFS.getPath(new File(TestUtils.tmpDir()).getCanonicalPath());
+ }
+
+ @Test
+ public void testConcurrentSymlinkModifications() throws Exception {
+ final Path xFile = workingDir.getRelative("file");
+ FileSystemUtils.createEmptyFile(xFile);
+
+ final Path xLinkToFile = workingDir.getRelative("link");
+
+ // "Boxed" for pass-by-reference.
+ final boolean[] run = { true };
+ final IOException[] exception = { null };
+ Thread createThread = new Thread() {
+ @Override
+ public void run() {
+ while (run[0]) {
+ if (!xLinkToFile.exists()) {
+ try {
+ xLinkToFile.createSymbolicLink(xFile);
+ } catch (IOException e) {
+ exception[0] = e;
+ return;
+ }
+ }
+ }
+ }
+ };
+ Thread deleteThread = new Thread() {
+ @Override
+ public void run() {
+ while (run[0]) {
+ if (xLinkToFile.exists(Symlinks.NOFOLLOW)) {
+ try {
+ xLinkToFile.delete();
+ } catch (IOException e) {
+ exception[0] = e;
+ return;
+ }
+ }
+ }
+ }
+ };
+ createThread.start();
+ deleteThread.start();
+ Thread.sleep(1000);
+ run[0] = false;
+ createThread.join(0);
+ deleteThread.join(0);
+
+ if (exception[0] != null) {
+ throw exception[0];
+ }
+ }
+
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
new file mode 100644
index 0000000000..b6e88d8976
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemTest.java
@@ -0,0 +1,1356 @@
+// Copyright 2014 Google Inc. 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Preconditions;
+import com.google.common.io.BaseEncoding;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.Fingerprint;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * This class handles the generic tests that any filesystem must pass.
+ *
+ * <p>Each filesystem-test should inherit from this class, thereby obtaining
+ * all the tests.
+ */
+public abstract class FileSystemTest {
+
+ private long savedTime;
+ protected FileSystem testFS;
+ protected boolean supportsSymlinks;
+ protected Path workingDir;
+
+ // Some useful examples of various kinds of files (mnemonic: "x" = "eXample")
+ protected Path xNothing;
+ protected Path xFile;
+ protected Path xNonEmptyDirectory;
+ protected Path xNonEmptyDirectoryFoo;
+ protected Path xEmptyDirectory;
+
+ @Before
+ public void setUp() throws Exception {
+ testFS = getFreshFileSystem();
+ workingDir = testFS.getPath(getTestTmpDir());
+ cleanUpWorkingDirectory(workingDir);
+ supportsSymlinks = testFS.supportsSymbolicLinks();
+
+ // % ls -lR
+ // -rw-rw-r-- xFile
+ // drwxrwxr-x xNonEmptyDirectory
+ // -rw-rw-r-- xNonEmptyDirectory/foo
+ // drwxrwxr-x xEmptyDirectory
+
+ xNothing = absolutize("xNothing");
+ xFile = absolutize("xFile");
+ xNonEmptyDirectory = absolutize("xNonEmptyDirectory");
+ xNonEmptyDirectoryFoo = xNonEmptyDirectory.getChild("foo");
+ xEmptyDirectory = absolutize("xEmptyDirectory");
+
+ FileSystemUtils.createEmptyFile(xFile);
+ xNonEmptyDirectory.createDirectory();
+ FileSystemUtils.createEmptyFile(xNonEmptyDirectoryFoo);
+ xEmptyDirectory.createDirectory();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ destroyFileSystem(testFS);
+ }
+
+ /**
+ * Returns an instance of the file system to test.
+ */
+ protected abstract FileSystem getFreshFileSystem() throws IOException;
+
+ protected boolean isSymbolicLink(File file) {
+ return com.google.devtools.build.lib.unix.FilesystemUtils.isSymbolicLink(file);
+ }
+
+ protected void setWritable(File file) throws IOException {
+ com.google.devtools.build.lib.unix.FilesystemUtils.setWritable(file);
+ }
+
+ protected void setExecutable(File file) throws IOException {
+ com.google.devtools.build.lib.unix.FilesystemUtils.setExecutable(file);
+ }
+
+ private static final Pattern STAT_SUBDIR_ERROR = Pattern.compile("(.*) \\(Not a directory\\)");
+
+ // Test that file is not present, using statIfFound. Base implementation throws an exception, but
+ // subclasses may override statIfFound to return null, in which case their tests should override
+ // this method.
+ @SuppressWarnings("unused") // Subclasses may throw.
+ protected void expectNotFound(Path path) throws IOException {
+ try {
+ assertNull(path.statIfFound());
+ } catch (IOException e) {
+ // May be because of a non-directory path component. Parse exception to check this.
+ Matcher matcher = STAT_SUBDIR_ERROR.matcher(e.getMessage());
+ if (!matcher.matches() || !path.getPathString().startsWith(matcher.group(1))) {
+ // Throw if this doesn't match what an ENOTDIR error looks like.
+ throw e;
+ }
+ }
+ }
+
+ /**
+ * Removes all stuff from the test filesystem.
+ */
+ protected void destroyFileSystem(FileSystem fileSystem) throws IOException {
+ Preconditions.checkArgument(fileSystem.equals(workingDir.getFileSystem()));
+ cleanUpWorkingDirectory(workingDir);
+ }
+
+ /**
+ * Cleans up the working directory by removing everything.
+ */
+ protected void cleanUpWorkingDirectory(Path workingPath)
+ throws IOException {
+ if (workingPath.exists()) {
+ removeEntireDirectory(workingPath.getPathFile()); // uses java.io.File!
+ }
+ FileSystemUtils.createDirectoryAndParents(workingPath);
+ }
+
+ /**
+ * This function removes an entire directory and all of its contents.
+ * Much like rm -rf directoryToRemove
+ */
+ protected void removeEntireDirectory(File directoryToRemove)
+ throws IOException {
+ // make sure that we do not remove anything outside the test directory
+ Path testDirPath = testFS.getPath(getTestTmpDir());
+ if (!testFS.getPath(directoryToRemove.getAbsolutePath()).startsWith(testDirPath)) {
+ throw new IOException("trying to remove files outside of the testdata directory");
+ }
+ // Some tests set the directories read-only and/or non-executable, so
+ // override that:
+ setWritable(directoryToRemove);
+ setExecutable(directoryToRemove);
+
+ File[] files = directoryToRemove.listFiles();
+ if (files != null) {
+ for (File currentFile : files) {
+ boolean isSymbolicLink = isSymbolicLink(currentFile);
+ if (!isSymbolicLink && currentFile.isDirectory()) {
+ removeEntireDirectory(currentFile);
+ } else {
+ if (!isSymbolicLink) {
+ setWritable(currentFile);
+ }
+ if (!currentFile.delete()) {
+ throw new IOException("Failed to delete '" + currentFile + "'");
+ }
+ }
+ }
+ }
+ if (!directoryToRemove.delete()) {
+ throw new IOException("Failed to delete '" + directoryToRemove + "'");
+ }
+ }
+
+ /**
+ * Returns the directory to use as the FileSystem's working directory.
+ * Canonicalized to make tests hermetic against symbolic links in TEST_TMPDIR.
+ */
+ protected final String getTestTmpDir() throws IOException {
+ return new File(TestUtils.tmpDir()).getCanonicalPath() + "/testdir";
+ }
+
+ /**
+ * Indirection to create links so we can test FileSystems that do not support
+ * link creation. For example, JavaFileSystemTest overrides this method
+ * and creates the link with an alternate FileSystem.
+ */
+ protected void createSymbolicLink(Path link, Path target) throws IOException {
+ createSymbolicLink(link, target.asFragment());
+ }
+
+ /**
+ * Indirection to create links so we can test FileSystems that do not support
+ * link creation. For example, JavaFileSystemTest overrides this method
+ * and creates the link with an alternate FileSystem.
+ */
+ protected void createSymbolicLink(Path link, PathFragment target) throws IOException {
+ link.createSymbolicLink(target);
+ }
+
+ /**
+ * Indirection to setReadOnly(false) on FileSystems that do not
+ * support setReadOnly(false). For example, JavaFileSystemTest overrides this
+ * method and makes the Path writable with an alternate FileSystem.
+ */
+ protected void makeWritable(Path target) throws IOException {
+ target.setWritable(true);
+ }
+
+ /**
+ * Indirection to {@link Path#setExecutable(boolean)} on FileSystems that do
+ * not support setExecutable. For example, JavaFileSystemTest overrides this
+ * method and makes the Path executable with an alternate FileSystem.
+ */
+ protected void setExecutable(Path target, boolean mode) throws IOException {
+ target.setExecutable(mode);
+ }
+
+ // TODO(bazel-team): (2011) Put in a setLastModifiedTime into the various objects
+ // and clobber the current time of the object we're currently handling.
+ // Otherwise testing the thing might get a little hard, depending on the clock.
+ void storeReferenceTime(long timeToMark) {
+ savedTime = timeToMark;
+ }
+
+ boolean isLaterThanreferenceTime(long testTime) {
+ return (savedTime <= testTime);
+ }
+
+ Path getTestFile() throws IOException {
+ Path tempPath = absolutize("test-file");
+ FileSystemUtils.createEmptyFile(tempPath);
+ return tempPath;
+ }
+
+ protected Path absolutize(String relativePathName) {
+ return workingDir.getRelative(relativePathName);
+ }
+
+ // Here the tests begin.
+
+ @Test
+ public void testIsFileForNonexistingPath() {
+ Path nonExistingPath = testFS.getPath("/something/strange");
+ assertFalse(nonExistingPath.isFile());
+ }
+
+ @Test
+ public void testIsDirectoryForNonexistingPath() {
+ Path nonExistingPath = testFS.getPath("/something/strange");
+ assertFalse(nonExistingPath.isDirectory());
+ }
+
+ @Test
+ public void testIsLinkForNonexistingPath() {
+ Path nonExistingPath = testFS.getPath("/something/strange");
+ assertFalse(nonExistingPath.isSymbolicLink());
+ }
+
+ @Test
+ public void testExistsForNonexistingPath() throws Exception {
+ Path nonExistingPath = testFS.getPath("/something/strange");
+ assertFalse(nonExistingPath.exists());
+ expectNotFound(nonExistingPath);
+ }
+
+ @Test
+ public void testBadPermissionsThrowsExceptionOnStatIfFound() throws Exception {
+ Path inaccessible = absolutize("inaccessible");
+ inaccessible.createDirectory();
+ Path child = inaccessible.getChild("child");
+ FileSystemUtils.createEmptyFile(child);
+ inaccessible.setExecutable(false);
+ assertFalse(child.exists());
+ try {
+ child.statIfFound();
+ fail();
+ } catch (IOException expected) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void testStatIfFoundReturnsNullForChildOfNonDir() throws Exception {
+ Path foo = absolutize("foo");
+ foo.createDirectory();
+ Path nonDir = foo.getRelative("bar");
+ FileSystemUtils.createEmptyFile(nonDir);
+ assertNull(nonDir.getRelative("file").statIfFound());
+ }
+
+ // The following tests check the handling of the current working directory.
+ @Test
+ public void testCreatePathRelativeToWorkingDirectory() {
+ Path relativeCreatedPath = absolutize("some-file");
+ Path expectedResult = workingDir.getRelative(new PathFragment("some-file"));
+
+ assertEquals(expectedResult, relativeCreatedPath);
+ }
+
+ // The following tests check the handling of the root directory
+ @Test
+ public void testRootIsDirectory() {
+ Path rootPath = testFS.getPath("/");
+ assertTrue(rootPath.isDirectory());
+ }
+
+ @Test
+ public void testRootHasNoParent() {
+ Path rootPath = testFS.getPath("/");
+ assertNull(rootPath.getParentDirectory());
+ }
+
+ // The following functions test the creation of files/links/directories.
+ @Test
+ public void testFileExists() throws Exception {
+ Path someFile = absolutize("some-file");
+ FileSystemUtils.createEmptyFile(someFile);
+ assertTrue(someFile.exists());
+ assertNotNull(someFile.statIfFound());
+ }
+
+ @Test
+ public void testFileIsFile() throws Exception {
+ Path someFile = absolutize("some-file");
+ FileSystemUtils.createEmptyFile(someFile);
+ assertTrue(someFile.isFile());
+ }
+
+ @Test
+ public void testFileIsNotDirectory() throws Exception {
+ Path someFile = absolutize("some-file");
+ FileSystemUtils.createEmptyFile(someFile);
+ assertFalse(someFile.isDirectory());
+ }
+
+ @Test
+ public void testFileIsNotSymbolicLink() throws Exception {
+ Path someFile = absolutize("some-file");
+ FileSystemUtils.createEmptyFile(someFile);
+ assertFalse(someFile.isSymbolicLink());
+ }
+
+ @Test
+ public void testDirectoryExists() throws Exception {
+ Path someDirectory = absolutize("some-dir");
+ someDirectory.createDirectory();
+ assertTrue(someDirectory.exists());
+ assertNotNull(someDirectory.statIfFound());
+ }
+
+ @Test
+ public void testDirectoryIsDirectory() throws Exception {
+ Path someDirectory = absolutize("some-dir");
+ someDirectory.createDirectory();
+ assertTrue(someDirectory.isDirectory());
+ }
+
+ @Test
+ public void testDirectoryIsNotFile() throws Exception {
+ Path someDirectory = absolutize("some-dir");
+ someDirectory.createDirectory();
+ assertFalse(someDirectory.isFile());
+ }
+
+ @Test
+ public void testDirectoryIsNotSymbolicLink() throws Exception {
+ Path someDirectory = absolutize("some-dir");
+ someDirectory.createDirectory();
+ assertFalse(someDirectory.isSymbolicLink());
+ }
+
+ @Test
+ public void testSymbolicFileLinkExists() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xFile);
+ assertTrue(someLink.exists());
+ assertNotNull(someLink.statIfFound());
+ }
+ }
+
+ @Test
+ public void testSymbolicFileLinkIsSymbolicLink() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xFile);
+ assertTrue(someLink.isSymbolicLink());
+ }
+ }
+
+ @Test
+ public void testSymbolicFileLinkIsFile() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xFile);
+ assertTrue(someLink.isFile());
+ }
+ }
+
+ @Test
+ public void testSymbolicFileLinkIsNotDirectory() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xFile);
+ assertFalse(someLink.isDirectory());
+ }
+ }
+
+ @Test
+ public void testSymbolicDirLinkExists() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xEmptyDirectory);
+ assertTrue(someLink.exists());
+ assertNotNull(someLink.statIfFound());
+ }
+ }
+
+ @Test
+ public void testSymbolicDirLinkIsSymbolicLink() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xEmptyDirectory);
+ assertTrue(someLink.isSymbolicLink());
+ }
+ }
+
+ @Test
+ public void testSymbolicDirLinkIsDirectory() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xEmptyDirectory);
+ assertTrue(someLink.isDirectory());
+ }
+ }
+
+ @Test
+ public void testSymbolicDirLinkIsNotFile() throws Exception {
+ if (supportsSymlinks) {
+ Path someLink = absolutize("some-link");
+ someLink.createSymbolicLink(xEmptyDirectory);
+ assertFalse(someLink.isFile());
+ }
+ }
+
+ @Test
+ public void testChildOfNonDirectory() throws Exception {
+ Path somePath = absolutize("file-name");
+ FileSystemUtils.createEmptyFile(somePath);
+ Path childOfNonDir = somePath.getChild("child");
+ assertFalse(childOfNonDir.exists());
+ expectNotFound(childOfNonDir);
+ }
+
+ @Test
+ public void testCreateDirectoryIsEmpty() throws Exception {
+ Path newPath = xEmptyDirectory.getChild("new-dir");
+ newPath.createDirectory();
+ assertEquals(newPath.getDirectoryEntries().size(), 0);
+ }
+
+ @Test
+ public void testCreateDirectoryIsOnlyChildInParent() throws Exception {
+ Path newPath = xEmptyDirectory.getChild("new-dir");
+ newPath.createDirectory();
+ assertEquals(1, newPath.getParentDirectory().getDirectoryEntries().size());
+ assertThat(newPath.getParentDirectory().getDirectoryEntries()).containsExactly(newPath);
+ }
+
+ @Test
+ public void testCreateDirectories() throws Exception {
+ Path newPath = absolutize("new-dir/sub/directory");
+ assertTrue(FileSystemUtils.createDirectoryAndParents(newPath));
+ }
+
+ @Test
+ public void testCreateDirectoriesIsDirectory() throws Exception {
+ Path newPath = absolutize("new-dir/sub/directory");
+ FileSystemUtils.createDirectoryAndParents(newPath);
+ assertTrue(newPath.isDirectory());
+ }
+
+ @Test
+ public void testCreateDirectoriesIsNotFile() throws Exception {
+ Path newPath = absolutize("new-dir/sub/directory");
+ FileSystemUtils.createDirectoryAndParents(newPath);
+ assertFalse(newPath.isFile());
+ }
+
+ @Test
+ public void testCreateDirectoriesIsNotSymbolicLink() throws Exception {
+ Path newPath = absolutize("new-dir/sub/directory");
+ FileSystemUtils.createDirectoryAndParents(newPath);
+ assertFalse(newPath.isSymbolicLink());
+ }
+
+ @Test
+ public void testCreateDirectoriesIsEmpty() throws Exception {
+ Path newPath = absolutize("new-dir/sub/directory");
+ FileSystemUtils.createDirectoryAndParents(newPath);
+ assertEquals(newPath.getDirectoryEntries().size(), 0);
+ }
+
+ @Test
+ public void testCreateDirectoriesIsOnlyChildInParent() throws Exception {
+ Path newPath = absolutize("new-dir/sub/directory");
+ FileSystemUtils.createDirectoryAndParents(newPath);
+ assertEquals(1, newPath.getParentDirectory().getDirectoryEntries().size());
+ assertThat(newPath.getParentDirectory().getDirectoryEntries()).containsExactly(newPath);
+ }
+
+ @Test
+ public void testCreateEmptyFileIsEmpty() throws Exception {
+ Path newPath = xEmptyDirectory.getChild("new-file");
+ FileSystemUtils.createEmptyFile(newPath);
+
+ assertEquals(newPath.getFileSize(), 0);
+ }
+
+ @Test
+ public void testCreateFileIsOnlyChildInParent() throws Exception {
+ Path newPath = xEmptyDirectory.getChild("new-file");
+ FileSystemUtils.createEmptyFile(newPath);
+ assertEquals(1, newPath.getParentDirectory().getDirectoryEntries().size());
+ assertThat(newPath.getParentDirectory().getDirectoryEntries()).containsExactly(newPath);
+ }
+
+ // The following functions test the behavior if errors occur during the
+ // creation of files/links/directories.
+ @Test
+ public void testCreateDirectoryWhereDirectoryAlreadyExists() throws Exception {
+ assertFalse(xEmptyDirectory.createDirectory());
+ }
+
+ @Test
+ public void testCreateDirectoryWhereFileAlreadyExists() {
+ try {
+ xFile.createDirectory();
+ fail();
+ } catch (IOException e) {
+ assertEquals(xFile + " (File exists)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateDirectoryWithoutExistingParent() throws Exception {
+ Path newPath = testFS.getPath("/deep/new-dir");
+ try {
+ newPath.createDirectory();
+ fail();
+ } catch (FileNotFoundException e) {
+ MoreAsserts.assertEndsWith(" (No such file or directory)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateDirectoryWithReadOnlyParent() throws Exception {
+ xEmptyDirectory.setWritable(false);
+ Path xChildOfReadonlyDir = xEmptyDirectory.getChild("x");
+ try {
+ xChildOfReadonlyDir.createDirectory();
+ fail();
+ } catch (IOException e) {
+ assertEquals(xChildOfReadonlyDir + " (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateFileWithoutExistingParent() throws Exception {
+ Path newPath = testFS.getPath("/non-existing-dir/new-file");
+ try {
+ FileSystemUtils.createEmptyFile(newPath);
+ fail();
+ } catch (FileNotFoundException e) {
+ MoreAsserts.assertEndsWith(" (No such file or directory)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateFileWithReadOnlyParent() throws Exception {
+ xEmptyDirectory.setWritable(false);
+ Path xChildOfReadonlyDir = xEmptyDirectory.getChild("x");
+ try {
+ FileSystemUtils.createEmptyFile(xChildOfReadonlyDir);
+ fail();
+ } catch (IOException e) {
+ assertEquals(xChildOfReadonlyDir + " (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateFileWithinFile() throws Exception {
+ Path newFilePath = absolutize("some-file");
+ FileSystemUtils.createEmptyFile(newFilePath);
+ Path wrongPath = absolutize("some-file/new-file");
+ try {
+ FileSystemUtils.createEmptyFile(wrongPath);
+ fail();
+ } catch (IOException e) {
+ MoreAsserts.assertEndsWith(" (Not a directory)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateDirectoryWithinFile() throws Exception {
+ Path newFilePath = absolutize("some-file");
+ FileSystemUtils.createEmptyFile(newFilePath);
+ Path wrongPath = absolutize("some-file/new-file");
+ try {
+ wrongPath.createDirectory();
+ fail();
+ } catch (IOException e) {
+ MoreAsserts.assertEndsWith(" (Not a directory)", e.getMessage());
+ }
+ }
+
+ // Test directory contents
+ @Test
+ public void testCreateMultipleChildren() throws Exception {
+ Path theDirectory = absolutize("foo/");
+ theDirectory.createDirectory();
+ Path newPath1 = absolutize("foo/new-file-1");
+ Path newPath2 = absolutize("foo/new-file-2");
+ Path newPath3 = absolutize("foo/new-file-3");
+
+ FileSystemUtils.createEmptyFile(newPath1);
+ FileSystemUtils.createEmptyFile(newPath2);
+ FileSystemUtils.createEmptyFile(newPath3);
+
+ assertThat(theDirectory.getDirectoryEntries()).containsExactly(newPath1, newPath2, newPath3);
+ }
+
+ @Test
+ public void testGetDirectoryEntriesThrowsExceptionWhenRunOnFile() throws Exception {
+ try {
+ xFile.getDirectoryEntries();
+ fail("No Exception thrown.");
+ } catch (IOException ex) {
+ if (ex instanceof FileNotFoundException) {
+ fail("The method should throw an object of class IOException.");
+ }
+ assertEquals(xFile + " (Not a directory)", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetDirectoryEntriesThrowsExceptionForNonexistingPath() {
+ Path somePath = testFS.getPath("/non-existing-path");
+ try {
+ somePath.getDirectoryEntries();
+ fail("FileNotFoundException not thrown.");
+ } catch (Exception x) {
+ assertEquals(somePath + " (No such file or directory)", x.getMessage());
+ }
+ }
+
+ // Test the removal of items
+ @Test
+ public void testDeleteDirectory() throws Exception {
+ assertTrue(xEmptyDirectory.delete());
+ }
+
+ @Test
+ public void testDeleteDirectoryIsNotDirectory() throws Exception {
+ xEmptyDirectory.delete();
+ assertFalse(xEmptyDirectory.isDirectory());
+ }
+
+ @Test
+ public void testDeleteDirectoryParentSize() throws Exception {
+ int parentSize = workingDir.getDirectoryEntries().size();
+ xEmptyDirectory.delete();
+ assertEquals(workingDir.getDirectoryEntries().size(), parentSize - 1);
+ }
+
+ @Test
+ public void testDeleteFile() throws Exception {
+ assertTrue(xFile.delete());
+ }
+
+ @Test
+ public void testDeleteFileIsNotFile() throws Exception {
+ xFile.delete();
+ assertFalse(xEmptyDirectory.isFile());
+ }
+
+ @Test
+ public void testDeleteFileParentSize() throws Exception {
+ int parentSize = workingDir.getDirectoryEntries().size();
+ xFile.delete();
+ assertEquals(workingDir.getDirectoryEntries().size(), parentSize - 1);
+ }
+
+ @Test
+ public void testDeleteRemovesCorrectFile() throws Exception {
+ Path newPath1 = xEmptyDirectory.getChild("new-file-1");
+ Path newPath2 = xEmptyDirectory.getChild("new-file-2");
+ Path newPath3 = xEmptyDirectory.getChild("new-file-3");
+
+ FileSystemUtils.createEmptyFile(newPath1);
+ FileSystemUtils.createEmptyFile(newPath2);
+ FileSystemUtils.createEmptyFile(newPath3);
+
+ assertTrue(newPath2.delete());
+ assertThat(xEmptyDirectory.getDirectoryEntries()).containsExactly(newPath1, newPath3);
+ }
+
+ @Test
+ public void testDeleteNonExistingDir() throws Exception {
+ Path path = xEmptyDirectory.getRelative("non-existing-dir");
+ assertFalse(path.delete());
+ }
+
+ @Test
+ public void testDeleteNotADirectoryPath() throws Exception {
+ Path path = xFile.getChild("new-file");
+ assertFalse(path.delete());
+ }
+
+ // Here we test the situations where delete should throw exceptions.
+ @Test
+ public void testDeleteNonEmptyDirectoryThrowsException() throws Exception {
+ try {
+ xNonEmptyDirectory.delete();
+ fail();
+ } catch (IOException e) {
+ assertEquals(xNonEmptyDirectory + " (Directory not empty)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testDeleteNonEmptyDirectoryNotDeletedDirectory() throws Exception {
+ try {
+ xNonEmptyDirectory.delete();
+ fail();
+ } catch (IOException e) {
+ // Expected
+ }
+
+ assertTrue(xNonEmptyDirectory.isDirectory());
+ }
+
+ @Test
+ public void testDeleteNonEmptyDirectoryNotDeletedFile() throws Exception {
+ try {
+ xNonEmptyDirectory.delete();
+ fail();
+ } catch (IOException e) {
+ // Expected
+ }
+
+ assertTrue(xNonEmptyDirectoryFoo.isFile());
+ }
+
+ @Test
+ public void testCannotRemoveRoot() {
+ Path rootDirectory = testFS.getRootDirectory();
+ try {
+ rootDirectory.delete();
+ fail();
+ } catch (IOException e) {
+ String msg = e.getMessage();
+ assertTrue(String.format("got %s want EBUSY or ENOTEMPTY", msg),
+ msg.endsWith(" (Directory not empty)")
+ || msg.endsWith(" (Device or resource busy)")
+ || msg.endsWith(" (Is a directory)")); // Happens on OS X.
+ }
+ }
+
+ // Test the date functions
+ @Test
+ public void testCreateFileChangesTimeOfDirectory() throws Exception {
+ storeReferenceTime(workingDir.getLastModifiedTime());
+ Path newPath = absolutize("new-file");
+ FileSystemUtils.createEmptyFile(newPath);
+ assertTrue(isLaterThanreferenceTime(workingDir.getLastModifiedTime()));
+ }
+
+ @Test
+ public void testRemoveFileChangesTimeOfDirectory() throws Exception {
+ Path newPath = absolutize("new-file");
+ FileSystemUtils.createEmptyFile(newPath);
+ storeReferenceTime(workingDir.getLastModifiedTime());
+ newPath.delete();
+ assertTrue(isLaterThanreferenceTime(workingDir.getLastModifiedTime()));
+ }
+
+ // This test is a little bit strange, as we cannot test the progression
+ // of the time directly. As the Java time and the OS time are slightly different.
+ // Therefore, we first create an unrelated file to get a notion
+ // of the current OS time and use that as a baseline.
+ @Test
+ public void testCreateFileTimestamp() throws Exception {
+ Path syncFile = absolutize("sync-file");
+ FileSystemUtils.createEmptyFile(syncFile);
+
+ Path newFile = absolutize("new-file");
+ storeReferenceTime(syncFile.getLastModifiedTime());
+ FileSystemUtils.createEmptyFile(newFile);
+ assertTrue(isLaterThanreferenceTime(newFile.getLastModifiedTime()));
+ }
+
+ @Test
+ public void testCreateDirectoryTimestamp() throws Exception {
+ Path syncFile = absolutize("sync-file");
+ FileSystemUtils.createEmptyFile(syncFile);
+
+ Path newPath = absolutize("new-dir");
+ storeReferenceTime(syncFile.getLastModifiedTime());
+ assertTrue(newPath.createDirectory());
+ assertTrue(isLaterThanreferenceTime(newPath.getLastModifiedTime()));
+ }
+
+ @Test
+ public void testWriteChangesModifiedTime() throws Exception {
+ storeReferenceTime(xFile.getLastModifiedTime());
+ FileSystemUtils.writeContentAsLatin1(xFile, "abc19");
+ assertTrue(isLaterThanreferenceTime(xFile.getLastModifiedTime()));
+ }
+
+ @Test
+ public void testGetLastModifiedTimeThrowsExceptionForNonexistingPath() throws Exception {
+ Path newPath = testFS.getPath("/non-existing-dir");
+ try {
+ newPath.getLastModifiedTime();
+ fail("FileNotFoundException not thrown!");
+ } catch (FileNotFoundException x) {
+ assertEquals(newPath + " (No such file or directory)", x.getMessage());
+ }
+ }
+
+ // Test file size
+ @Test
+ public void testFileSizeThrowsExceptionForNonexistingPath() throws Exception {
+ Path newPath = testFS.getPath("/non-existing-file");
+ try {
+ newPath.getFileSize();
+ fail("FileNotFoundException not thrown.");
+ } catch (FileNotFoundException e) {
+ assertEquals(newPath + " (No such file or directory)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testFileSizeAfterWrite() throws Exception {
+ String testData = "abc19";
+
+ FileSystemUtils.writeContentAsLatin1(xFile, testData);
+ assertEquals(testData.length(), xFile.getFileSize());
+ }
+
+ // Testing the input/output routines
+ @Test
+ public void testFileWriteAndReadAsLatin1() throws Exception {
+ String testData = "abc19";
+
+ FileSystemUtils.writeContentAsLatin1(xFile, testData);
+ String resultData = new String(FileSystemUtils.readContentAsLatin1(xFile));
+
+ assertEquals(testData,resultData);
+ }
+
+ @Test
+ public void testInputAndOutputStreamEOF() throws Exception {
+ OutputStream outStream = xFile.getOutputStream();
+ outStream.write(1);
+ outStream.close();
+
+ InputStream inStream = xFile.getInputStream();
+ inStream.read();
+ assertEquals(-1, inStream.read());
+ inStream.close();
+ }
+
+ @Test
+ public void testInputAndOutputStream() throws Exception {
+ OutputStream outStream = xFile.getOutputStream();
+ for (int i = 33; i < 126; i++) {
+ outStream.write(i);
+ }
+ outStream.close();
+
+ InputStream inStream = xFile.getInputStream();
+ for (int i = 33; i < 126; i++) {
+ int readValue = inStream.read();
+ assertEquals(i,readValue);
+ }
+ inStream.close();
+ }
+
+ @Test
+ public void testInputAndOutputStreamAppend() throws Exception {
+ OutputStream outStream = xFile.getOutputStream();
+ for (int i = 33; i < 126; i++) {
+ outStream.write(i);
+ }
+ outStream.close();
+
+ OutputStream appendOut = xFile.getOutputStream(true);
+ for (int i = 126; i < 155; i++) {
+ appendOut.write(i);
+ }
+ appendOut.close();
+
+ InputStream inStream = xFile.getInputStream();
+ for (int i = 33; i < 155; i++) {
+ int readValue = inStream.read();
+ assertEquals(i,readValue);
+ }
+ inStream.close();
+ }
+
+ @Test
+ public void testInputAndOutputStreamNoAppend() throws Exception {
+ OutputStream outStream = xFile.getOutputStream();
+ outStream.write(1);
+ outStream.close();
+
+ OutputStream noAppendOut = xFile.getOutputStream(false);
+ noAppendOut.close();
+
+ InputStream inStream = xFile.getInputStream();
+ assertEquals(-1, inStream.read());
+ inStream.close();
+ }
+
+ @Test
+ public void testGetOutputStreamCreatesFile() throws Exception {
+ Path newFile = absolutize("does_not_exist_yet.txt");
+
+ OutputStream out = newFile.getOutputStream();
+ out.write(42);
+ out.close();
+
+ assertTrue(newFile.isFile());
+ }
+
+ @Test
+ public void testInpuStreamThrowExceptionOnDirectory() throws Exception {
+ try {
+ xEmptyDirectory.getOutputStream();
+ fail("The Exception was not thrown!");
+ } catch (IOException ex) {
+ assertEquals(xEmptyDirectory + " (Is a directory)", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testOutputStreamThrowExceptionOnDirectory() throws Exception {
+ try {
+ xEmptyDirectory.getInputStream();
+ fail("The Exception was not thrown!");
+ } catch (IOException ex) {
+ assertEquals(xEmptyDirectory + " (Is a directory)", ex.getMessage());
+ }
+ }
+
+ // Test renaming
+ @Test
+ public void testCanRenameToUnusedName() throws Exception {
+ xFile.renameTo(xNothing);
+ assertFalse(xFile.exists());
+ assertTrue(xNothing.isFile());
+ }
+
+ @Test
+ public void testCanRenameFileToExistingFile() throws Exception {
+ Path otherFile = absolutize("otherFile");
+ FileSystemUtils.createEmptyFile(otherFile);
+ xFile.renameTo(otherFile); // succeeds
+ assertFalse(xFile.exists());
+ assertTrue(otherFile.isFile());
+ }
+
+ @Test
+ public void testCanRenameDirToExistingEmptyDir() throws Exception {
+ xNonEmptyDirectory.renameTo(xEmptyDirectory); // succeeds
+ assertFalse(xNonEmptyDirectory.exists());
+ assertTrue(xEmptyDirectory.isDirectory());
+ assertFalse(xEmptyDirectory.getDirectoryEntries().isEmpty());
+ }
+
+ @Test
+ public void testCantRenameDirToExistingNonEmptyDir() throws Exception {
+ try {
+ xEmptyDirectory.renameTo(xNonEmptyDirectory);
+ fail();
+ } catch (IOException e) {
+ MoreAsserts.assertEndsWith(" (Directory not empty)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCantRenameDirToExistingNonEmptyDirNothingChanged() throws Exception {
+ try {
+ xEmptyDirectory.renameTo(xNonEmptyDirectory);
+ fail();
+ } catch (IOException e) {
+ // Expected
+ }
+
+ assertTrue(xNonEmptyDirectory.isDirectory());
+ assertTrue(xEmptyDirectory.isDirectory());
+ assertTrue(xEmptyDirectory.getDirectoryEntries().isEmpty());
+ assertFalse(xNonEmptyDirectory.getDirectoryEntries().isEmpty());
+ }
+
+ @Test
+ public void testCantRenameDirToExistingFile() {
+ try {
+ xEmptyDirectory.renameTo(xFile);
+ fail();
+ } catch (IOException e) {
+ assertEquals(xEmptyDirectory + " -> " + xFile + " (Not a directory)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCantRenameDirToExistingFileNothingChanged() {
+ try {
+ xEmptyDirectory.renameTo(xFile);
+ fail();
+ } catch (IOException e) {
+ // Expected
+ }
+
+ assertTrue(xEmptyDirectory.isDirectory());
+ assertTrue(xFile.isFile());
+ }
+
+ @Test
+ public void testCantRenameFileToExistingDir() {
+ try {
+ xFile.renameTo(xEmptyDirectory);
+ fail();
+ } catch (IOException e) {
+ assertEquals(xFile + " -> " + xEmptyDirectory + " (Is a directory)",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCantRenameFileToExistingDirNothingChanged() {
+ try {
+ xFile.renameTo(xEmptyDirectory);
+ fail();
+ } catch (IOException e) {
+ // Expected
+ }
+
+ assertTrue(xEmptyDirectory.isDirectory());
+ assertTrue(xFile.isFile());
+ }
+
+ @Test
+ public void testMoveOnNonExistingFileThrowsException() throws Exception {
+ Path nonExistingPath = absolutize("non-existing");
+ Path targetPath = absolutize("does-not-matter");
+ try {
+ nonExistingPath.renameTo(targetPath);
+ fail();
+ } catch (FileNotFoundException e) {
+ MoreAsserts.assertEndsWith(" (No such file or directory)", e.getMessage());
+ }
+ }
+
+ // Test the Paths
+ @Test
+ public void testGetPathOnlyAcceptsAbsolutePath() {
+ try {
+ testFS.getPath("not-absolute");
+ fail("The expected Exception was not thrown.");
+ } catch (IllegalArgumentException ex) {
+ assertEquals("not-absolute (not an absolute path)", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testGetPathOnlyAcceptsAbsolutePathFragment() {
+ try {
+ testFS.getPath(new PathFragment("not-absolute"));
+ fail("The expected Exception was not thrown.");
+ } catch (IllegalArgumentException ex) {
+ assertEquals("not-absolute (not an absolute path)", ex.getMessage());
+ }
+ }
+
+ // Test the access permissions
+ @Test
+ public void testNewFilesAreWritable() throws Exception {
+ assertTrue(xFile.isWritable());
+ }
+
+ @Test
+ public void testNewFilesAreReadable() throws Exception {
+ assertTrue(xFile.isReadable());
+ }
+
+ @Test
+ public void testNewDirsAreWritable() throws Exception {
+ assertTrue(xEmptyDirectory.isWritable());
+ }
+
+ @Test
+ public void testNewDirsAreReadable() throws Exception {
+ assertTrue(xEmptyDirectory.isReadable());
+ }
+
+ @Test
+ public void testNewDirsAreExecutable() throws Exception {
+ assertTrue(xEmptyDirectory.isExecutable());
+ }
+
+ @Test
+ public void testCannotGetExecutableOnNonexistingFile() throws Exception {
+ try {
+ xNothing.isExecutable();
+ fail("No exception thrown.");
+ } catch (FileNotFoundException ex) {
+ assertEquals(xNothing + " (No such file or directory)", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotSetExecutableOnNonexistingFile() throws Exception {
+ try {
+ xNothing.setExecutable(true);
+ fail("No exception thrown.");
+ } catch (FileNotFoundException ex) {
+ assertEquals(xNothing + " (No such file or directory)", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotGetWritableOnNonexistingFile() throws Exception {
+ try {
+ xNothing.isWritable();
+ fail("No exception thrown.");
+ } catch (FileNotFoundException ex) {
+ assertEquals(xNothing + " (No such file or directory)", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotSetWritableOnNonexistingFile() throws Exception {
+ try {
+ xNothing.setWritable(false);
+ fail("No exception thrown.");
+ } catch (FileNotFoundException ex) {
+ assertEquals(xNothing + " (No such file or directory)", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetReadableOnFile() throws Exception {
+ xFile.setReadable(false);
+ assertFalse(xFile.isReadable());
+ xFile.setReadable(true);
+ assertTrue(xFile.isReadable());
+ }
+
+ @Test
+ public void testSetWritableOnFile() throws Exception {
+ xFile.setWritable(false);
+ assertFalse(xFile.isWritable());
+ xFile.setWritable(true);
+ assertTrue(xFile.isWritable());
+ }
+
+ @Test
+ public void testSetExecutableOnFile() throws Exception {
+ xFile.setExecutable(true);
+ assertTrue(xFile.isExecutable());
+ xFile.setExecutable(false);
+ assertFalse(xFile.isExecutable());
+ }
+
+ @Test
+ public void testSetExecutableOnDirectory() throws Exception {
+ setExecutable(xNonEmptyDirectory, false);
+
+ try {
+ // We can't map names->inodes in a non-executable directory:
+ xNonEmptyDirectoryFoo.isWritable(); // i.e. stat
+ fail();
+ } catch (IOException e) {
+ MoreAsserts.assertEndsWith(" (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testWritingToReadOnlyFileThrowsException() throws Exception {
+ xFile.setWritable(false);
+ try {
+ FileSystemUtils.writeContent(xFile, "hello, world!".getBytes());
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ assertEquals(xFile + " (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testReadingFromUnreadableFileThrowsException() throws Exception {
+ FileSystemUtils.writeContent(xFile, "hello, world!".getBytes());
+ xFile.setReadable(false);
+ try {
+ FileSystemUtils.readContent(xFile);
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ assertEquals(xFile + " (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateFileInReadOnlyDirectory() throws Exception {
+ Path xNonEmptyDirectoryBar = xNonEmptyDirectory.getChild("bar");
+ xNonEmptyDirectory.setWritable(false);
+
+ try {
+ FileSystemUtils.createEmptyFile(xNonEmptyDirectoryBar);
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ assertEquals(xNonEmptyDirectoryBar + " (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateDirectoryInReadOnlyDirectory() throws Exception {
+ Path xNonEmptyDirectoryBar = xNonEmptyDirectory.getChild("bar");
+ xNonEmptyDirectory.setWritable(false);
+
+ try {
+ xNonEmptyDirectoryBar.createDirectory();
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ assertEquals(xNonEmptyDirectoryBar + " (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotMoveIntoReadOnlyDirectory() throws Exception {
+ Path xNonEmptyDirectoryBar = xNonEmptyDirectory.getChild("bar");
+ xNonEmptyDirectory.setWritable(false);
+
+ try {
+ xFile.renameTo(xNonEmptyDirectoryBar);
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ MoreAsserts.assertEndsWith(" (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotMoveFromReadOnlyDirectory() throws Exception {
+ xNonEmptyDirectory.setWritable(false);
+
+ try {
+ xNonEmptyDirectoryFoo.renameTo(xNothing);
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ MoreAsserts.assertEndsWith(" (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotDeleteInReadOnlyDirectory() throws Exception {
+ xNonEmptyDirectory.setWritable(false);
+
+ try {
+ xNonEmptyDirectoryFoo.delete();
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ assertEquals(xNonEmptyDirectoryFoo + " (Permission denied)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreatSymbolicLinkInReadOnlyDirectory() throws Exception {
+ Path xNonEmptyDirectoryBar = xNonEmptyDirectory.getChild("bar");
+ xNonEmptyDirectory.setWritable(false);
+
+ if (supportsSymlinks) {
+ try {
+ createSymbolicLink(xNonEmptyDirectoryBar, xNonEmptyDirectoryFoo);
+ fail("No exception thrown.");
+ } catch (IOException e) {
+ assertEquals(xNonEmptyDirectoryBar + " (Permission denied)", e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testGetMD5DigestForEmptyFile() throws Exception {
+ Fingerprint fp = new Fingerprint();
+ fp.addBytes(new byte[0]);
+ assertEquals(BaseEncoding.base16().lowerCase().encode(xFile.getMD5Digest()),
+ fp.hexDigestAndReset());
+ }
+
+ @Test
+ public void testGetMD5Digest() throws Exception {
+ byte[] buffer = new byte[500000];
+ for (int i = 0; i < buffer.length; ++i) {
+ buffer[i] = 1;
+ }
+ FileSystemUtils.writeContent(xFile, buffer);
+ Fingerprint fp = new Fingerprint();
+ fp.addBytes(buffer);
+ assertEquals(BaseEncoding.base16().lowerCase().encode(xFile.getMD5Digest()),
+ fp.hexDigestAndReset());
+ }
+
+ @Test
+ public void testStatFailsFastOnNonExistingFiles() throws Exception {
+ try {
+ xNothing.stat();
+ fail("Expected IOException");
+ } catch(IOException e) {
+ // Do nothing.
+ }
+ }
+
+ @Test
+ public void testStatNullableFailsFastOnNonExistingFiles() throws Exception {
+ assertNull(xNothing.statNullable());
+ }
+
+ @Test
+ public void testResolveSymlinks() throws Exception {
+ if (supportsSymlinks) {
+ createSymbolicLink(xNothing, xFile);
+ FileSystemUtils.createEmptyFile(xFile);
+ assertEquals(xFile.asFragment(), testFS.resolveOneLink(xNothing));
+ assertEquals(xFile, xNothing.resolveSymbolicLinks());
+ }
+ }
+
+ @Test
+ public void testResolveNonSymlinks() throws Exception {
+ if (supportsSymlinks) {
+ assertEquals(null, testFS.resolveOneLink(xFile));
+ assertEquals(xFile, xFile.resolveSymbolicLinks());
+ }
+ }
+
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
new file mode 100644
index 0000000000..21ca39b8f0
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemUtilsTest.java
@@ -0,0 +1,878 @@
+// Copyright 2014 Google Inc. 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 static com.google.devtools.build.lib.vfs.FileSystemUtils.appendWithoutExtension;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.commonAncestor;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.copyFile;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.copyTool;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.createDirectoryAndParents;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.deleteTree;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.deleteTreesBelowNotPrefixed;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.longestPathPrefix;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.plantLinkForest;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.relativePath;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.removeExtension;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.touchFile;
+import static com.google.devtools.build.lib.vfs.FileSystemUtils.traverseTree;
+import static java.nio.charset.StandardCharsets.ISO_8859_1;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.testutil.BlazeTestUtils;
+import com.google.devtools.build.lib.testutil.ManualClock;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class tests the file system utilities.
+ */
+@RunWith(JUnit4.class)
+public class FileSystemUtilsTest {
+ private ManualClock clock;
+ private FileSystem fileSystem;
+ private Path workingDir;
+
+ @Before
+ public void setUp() throws Exception {
+ clock = new ManualClock();
+ fileSystem = new InMemoryFileSystem(clock);
+ workingDir = fileSystem.getPath("/workingDir");
+ }
+
+ Path topDir;
+ Path file1;
+ Path file2;
+ Path aDir;
+ Path file3;
+ Path innerDir;
+ Path link1;
+ Path dirLink;
+ Path file4;
+
+ /*
+ * Build a directory tree that looks like:
+ * top-dir/
+ * file-1
+ * file-2
+ * a-dir/
+ * file-3
+ * inner-dir/
+ * link-1 => file-4
+ * dir-link => a-dir
+ * file-4
+ */
+ private void createTestDirectoryTree() throws IOException {
+ topDir = fileSystem.getPath("/top-dir");
+ file1 = fileSystem.getPath("/top-dir/file-1");
+ file2 = fileSystem.getPath("/top-dir/file-2");
+ aDir = fileSystem.getPath("/top-dir/a-dir");
+ file3 = fileSystem.getPath("/top-dir/a-dir/file-3");
+ innerDir = fileSystem.getPath("/top-dir/a-dir/inner-dir");
+ link1 = fileSystem.getPath("/top-dir/a-dir/inner-dir/link-1");
+ dirLink = fileSystem.getPath("/top-dir/a-dir/inner-dir/dir-link");
+ file4 = fileSystem.getPath("/file-4");
+
+ topDir.createDirectory();
+ FileSystemUtils.createEmptyFile(file1);
+ FileSystemUtils.createEmptyFile(file2);
+ aDir.createDirectory();
+ FileSystemUtils.createEmptyFile(file3);
+ innerDir.createDirectory();
+ link1.createSymbolicLink(file4); // simple symlink
+ dirLink.createSymbolicLink(aDir); // creates link loop
+ FileSystemUtils.createEmptyFile(file4);
+ }
+
+ private void checkTestDirectoryTreesBelow(Path toPath) throws IOException {
+ Path copiedFile1 = toPath.getChild("file-1");
+ assertTrue(copiedFile1.exists());
+ assertTrue(copiedFile1.isFile());
+
+ Path copiedFile2 = toPath.getChild("file-2");
+ assertTrue(copiedFile2.exists());
+ assertTrue(copiedFile2.isFile());
+
+ Path copiedADir = toPath.getChild("a-dir");
+ assertTrue(copiedADir.exists());
+ assertTrue(copiedADir.isDirectory());
+ Collection<Path> aDirEntries = copiedADir.getDirectoryEntries();
+ assertEquals(2, aDirEntries.size());
+
+ Path copiedFile3 = copiedADir.getChild("file-3");
+ assertTrue(copiedFile3.exists());
+ assertTrue(copiedFile3.isFile());
+
+ Path copiedInnerDir = copiedADir.getChild("inner-dir");
+ assertTrue(copiedInnerDir.exists());
+ assertTrue(copiedInnerDir.isDirectory());
+
+ Path copiedLink1 = copiedInnerDir.getChild("link-1");
+ assertTrue(copiedLink1.exists());
+ assertTrue(copiedLink1.isSymbolicLink());
+ assertEquals(copiedLink1.resolveSymbolicLinks(), file4);
+
+ Path copiedDirLink = copiedInnerDir.getChild("dir-link");
+ assertTrue(copiedDirLink.exists());
+ assertTrue(copiedDirLink.isSymbolicLink());
+ assertEquals(copiedDirLink.resolveSymbolicLinks(), aDir);
+ }
+
+ // tests
+
+ @Test
+ public void testChangeModtime() throws IOException {
+ Path file = fileSystem.getPath("/my-file");
+ try {
+ BlazeTestUtils.changeModtime(file);
+ fail();
+ } catch (FileNotFoundException e) {
+ /* ok */
+ }
+ FileSystemUtils.createEmptyFile(file);
+ long prevMtime = file.getLastModifiedTime();
+ BlazeTestUtils.changeModtime(file);
+ assertFalse(prevMtime == file.getLastModifiedTime());
+ }
+
+ @Test
+ public void testCommonAncestor() {
+ assertEquals(topDir, commonAncestor(topDir, topDir));
+ assertEquals(topDir, commonAncestor(file1, file3));
+ assertEquals(topDir, commonAncestor(file1, dirLink));
+ }
+
+ @Test
+ public void testRelativePath() throws IOException {
+ createTestDirectoryTree();
+ assertEquals("file-1", relativePath(topDir, file1).getPathString());
+ assertEquals(".", relativePath(topDir, topDir).getPathString());
+ assertEquals("a-dir/inner-dir/dir-link", relativePath(topDir, dirLink).getPathString());
+ assertEquals("../file-4", relativePath(topDir, file4).getPathString());
+ assertEquals("../../../file-4", relativePath(innerDir, file4).getPathString());
+ }
+
+ private String longestPathPrefixStr(String path, String... prefixStrs) {
+ Set<PathFragment> prefixes = new HashSet<>();
+ for (String prefix : prefixStrs) {
+ prefixes.add(new PathFragment(prefix));
+ }
+ PathFragment longest = longestPathPrefix(new PathFragment(path), prefixes);
+ return longest != null ? longest.getPathString() : null;
+ }
+
+ @Test
+ public void testLongestPathPrefix() {
+ assertEquals("A", longestPathPrefixStr("A/b", "A", "B")); // simple parent
+ assertEquals("A", longestPathPrefixStr("A", "A", "B")); // self
+ assertEquals("A/B", longestPathPrefixStr("A/B/c", "A", "A/B")); // want longest
+ assertNull(longestPathPrefixStr("C/b", "A", "B")); // not found in other parents
+ assertNull(longestPathPrefixStr("A", "A/B", "B")); // not found in child
+ assertEquals("A/B/C", longestPathPrefixStr("A/B/C/d/e/f.h", "A/B/C", "B/C/d"));
+ }
+
+ @Test
+ public void testRemoveExtension_Strings() throws Exception {
+ assertEquals("foo", removeExtension("foo.c"));
+ assertEquals("a/foo", removeExtension("a/foo.c"));
+ assertEquals("a.b/foo", removeExtension("a.b/foo"));
+ assertEquals("foo", removeExtension("foo"));
+ assertEquals("foo", removeExtension("foo."));
+ }
+
+ @Test
+ public void testRemoveExtension_Paths() throws Exception {
+ assertPath("/foo", removeExtension(fileSystem.getPath("/foo.c")));
+ assertPath("/a/foo", removeExtension(fileSystem.getPath("/a/foo.c")));
+ assertPath("/a.b/foo", removeExtension(fileSystem.getPath("/a.b/foo")));
+ assertPath("/foo", removeExtension(fileSystem.getPath("/foo")));
+ assertPath("/foo", removeExtension(fileSystem.getPath("/foo.")));
+ }
+
+ private static void assertPath(String expected, PathFragment actual) {
+ assertEquals(expected, actual.getPathString());
+ }
+
+ private static void assertPath(String expected, Path actual) {
+ assertEquals(expected, actual.getPathString());
+ }
+
+ @Test
+ public void testReplaceExtension_Path() throws Exception {
+ assertPath("/foo/bar.baz",
+ FileSystemUtils.replaceExtension(fileSystem.getPath("/foo/bar"), ".baz"));
+ assertPath("/foo/bar.baz",
+ FileSystemUtils.replaceExtension(fileSystem.getPath("/foo/bar.cc"), ".baz"));
+ assertPath("/foo.baz", FileSystemUtils.replaceExtension(fileSystem.getPath("/foo/"), ".baz"));
+ assertPath("/foo.baz",
+ FileSystemUtils.replaceExtension(fileSystem.getPath("/foo.cc/"), ".baz"));
+ assertPath("/foo.baz", FileSystemUtils.replaceExtension(fileSystem.getPath("/foo"), ".baz"));
+ assertPath("/foo.baz", FileSystemUtils.replaceExtension(fileSystem.getPath("/foo.cc"), ".baz"));
+ assertPath("/.baz", FileSystemUtils.replaceExtension(fileSystem.getPath("/.cc"), ".baz"));
+ assertEquals(null, FileSystemUtils.replaceExtension(fileSystem.getPath("/"), ".baz"));
+ }
+
+ @Test
+ public void testReplaceExtension_PathFragment() throws Exception {
+ assertPath("foo/bar.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("foo/bar"), ".baz"));
+ assertPath("foo/bar.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("foo/bar.cc"), ".baz"));
+ assertPath("/foo/bar.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("/foo/bar"), ".baz"));
+ assertPath("/foo/bar.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("/foo/bar.cc"), ".baz"));
+ assertPath("foo.baz", FileSystemUtils.replaceExtension(new PathFragment("foo/"), ".baz"));
+ assertPath("foo.baz", FileSystemUtils.replaceExtension(new PathFragment("foo.cc/"), ".baz"));
+ assertPath("/foo.baz", FileSystemUtils.replaceExtension(new PathFragment("/foo/"), ".baz"));
+ assertPath("/foo.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("/foo.cc/"), ".baz"));
+ assertPath("foo.baz", FileSystemUtils.replaceExtension(new PathFragment("foo"), ".baz"));
+ assertPath("foo.baz", FileSystemUtils.replaceExtension(new PathFragment("foo.cc"), ".baz"));
+ assertPath("/foo.baz", FileSystemUtils.replaceExtension(new PathFragment("/foo"), ".baz"));
+ assertPath("/foo.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("/foo.cc"), ".baz"));
+ assertPath(".baz", FileSystemUtils.replaceExtension(new PathFragment(".cc"), ".baz"));
+ assertEquals(null, FileSystemUtils.replaceExtension(new PathFragment("/"), ".baz"));
+ assertEquals(null, FileSystemUtils.replaceExtension(new PathFragment(""), ".baz"));
+ assertPath("foo/bar.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("foo/bar.pony"), ".baz", ".pony"));
+ assertPath("foo/bar.baz",
+ FileSystemUtils.replaceExtension(new PathFragment("foo/bar"), ".baz", ""));
+ assertEquals(null, FileSystemUtils.replaceExtension(new PathFragment(""), ".baz", ".pony"));
+ assertEquals(null,
+ FileSystemUtils.replaceExtension(new PathFragment("foo/bar.pony"), ".baz", ".unicorn"));
+ }
+
+ @Test
+ public void testAppendWithoutExtension() throws Exception {
+ assertPath("libfoo-src.jar",
+ appendWithoutExtension(new PathFragment("libfoo.jar"), "-src"));
+ assertPath("foo/libfoo-src.jar",
+ appendWithoutExtension(new PathFragment("foo/libfoo.jar"), "-src"));
+ assertPath("java/com/google/foo/libfoo-src.jar",
+ appendWithoutExtension(new PathFragment("java/com/google/foo/libfoo.jar"), "-src"));
+ assertPath("libfoo.bar-src.jar",
+ appendWithoutExtension(new PathFragment("libfoo.bar.jar"), "-src"));
+ assertPath("libfoo-src",
+ appendWithoutExtension(new PathFragment("libfoo"), "-src"));
+ assertPath("libfoo-src.jar",
+ appendWithoutExtension(new PathFragment("libfoo.jar/"), "-src"));
+ assertPath("libfoo.src.jar",
+ appendWithoutExtension(new PathFragment("libfoo.jar"), ".src"));
+ assertEquals(null, appendWithoutExtension(new PathFragment("/"), "-src"));
+ assertEquals(null, appendWithoutExtension(new PathFragment(""), "-src"));
+ }
+
+ @Test
+ public void testReplaceSegments() {
+ assertPath(
+ "poo/bar/baz.cc",
+ FileSystemUtils.replaceSegments(new PathFragment("foo/bar/baz.cc"), "foo", "poo", true));
+ assertPath(
+ "poo/poo/baz.cc",
+ FileSystemUtils.replaceSegments(new PathFragment("foo/foo/baz.cc"), "foo", "poo", true));
+ assertPath(
+ "poo/foo/baz.cc",
+ FileSystemUtils.replaceSegments(new PathFragment("foo/foo/baz.cc"), "foo", "poo", false));
+ assertPath(
+ "foo/bar/baz.cc",
+ FileSystemUtils.replaceSegments(new PathFragment("foo/bar/baz.cc"), "boo", "poo", true));
+ }
+
+ @Test
+ public void testGetWorkingDirectory() {
+ String userDir = System.getProperty("user.dir");
+
+ assertEquals(FileSystemUtils.getWorkingDirectory(fileSystem),
+ fileSystem.getPath(System.getProperty("user.dir", "/")));
+
+ System.setProperty("user.dir", "/blah/blah/blah");
+ assertEquals(FileSystemUtils.getWorkingDirectory(fileSystem),
+ fileSystem.getPath("/blah/blah/blah"));
+
+ System.setProperty("user.dir", userDir);
+ }
+
+ @Test
+ public void testResolveRelativeToFilesystemWorkingDir() {
+ PathFragment relativePath = new PathFragment("relative/path");
+ assertEquals(workingDir.getRelative(relativePath),
+ workingDir.getRelative(relativePath));
+
+ PathFragment absolutePath = new PathFragment("/absolute/path");
+ assertEquals(fileSystem.getPath(absolutePath),
+ workingDir.getRelative(absolutePath));
+ }
+
+ @Test
+ public void testTouchFileCreatesFile() throws IOException {
+ createTestDirectoryTree();
+ Path nonExistingFile = fileSystem.getPath("/previously-non-existing");
+ assertFalse(nonExistingFile.exists());
+ touchFile(nonExistingFile);
+
+ assertTrue(nonExistingFile.exists());
+ }
+
+ @Test
+ public void testTouchFileAdjustsFileTime() throws IOException {
+ createTestDirectoryTree();
+ Path testFile = file4;
+ long oldTime = testFile.getLastModifiedTime();
+ testFile.setLastModifiedTime(42);
+ touchFile(testFile);
+
+ assertTrue(testFile.getLastModifiedTime() >= oldTime);
+ }
+
+ @Test
+ public void testCopyFile() throws IOException {
+ createTestDirectoryTree();
+ Path originalFile = file1;
+ byte[] content = new byte[] { 'a', 'b', 'c', 23, 42 };
+ FileSystemUtils.writeContent(originalFile, content);
+
+ Path copyTarget = file2;
+
+ copyFile(originalFile, copyTarget);
+
+ assertTrue(Arrays.equals(content, FileSystemUtils.readContent(copyTarget)));
+ }
+
+ @Test
+ public void testReadContentWithLimit() throws IOException {
+ createTestDirectoryTree();
+ String str = "this is a test of readContentWithLimit method";
+ FileSystemUtils.writeContent(file1, StandardCharsets.ISO_8859_1, str);
+ assertEquals(readStringFromFile(file1, 0), "");
+ assertEquals(readStringFromFile(file1, 10), str.substring(0, 10));
+ assertEquals(readStringFromFile(file1, 1000000), str);
+ }
+
+ private String readStringFromFile(Path file, int limit) throws IOException {
+ byte[] bytes = FileSystemUtils.readContentWithLimit(file, limit);
+ return new String(bytes, StandardCharsets.ISO_8859_1);
+ }
+
+ @Test
+ public void testAppend() throws IOException {
+ createTestDirectoryTree();
+ FileSystemUtils.writeIsoLatin1(file1, "nobody says ");
+ FileSystemUtils.writeIsoLatin1(file1, "mary had");
+ FileSystemUtils.appendIsoLatin1(file1, "a little lamb");
+ assertEquals(
+ "mary had\na little lamb\n",
+ new String(FileSystemUtils.readContentAsLatin1(file1)));
+ }
+
+ @Test
+ public void testCopyFileAttributes() throws IOException {
+ createTestDirectoryTree();
+ Path originalFile = file1;
+ byte[] content = new byte[] { 'a', 'b', 'c', 23, 42 };
+ FileSystemUtils.writeContent(originalFile, content);
+ file1.setLastModifiedTime(12345L);
+ file1.setWritable(false);
+ file1.setExecutable(false);
+
+ Path copyTarget = file2;
+ copyFile(originalFile, copyTarget);
+
+ assertEquals(12345L, file2.getLastModifiedTime());
+ assertFalse(file2.isExecutable());
+ assertFalse(file2.isWritable());
+
+ file1.setWritable(true);
+ file1.setExecutable(true);
+
+ copyFile(originalFile, copyTarget);
+
+ assertEquals(12345L, file2.getLastModifiedTime());
+ assertTrue(file2.isExecutable());
+ assertTrue(file2.isWritable());
+
+ }
+
+ @Test
+ public void testCopyFileThrowsExceptionIfTargetCantBeDeleted() throws IOException {
+ createTestDirectoryTree();
+ Path originalFile = file1;
+ byte[] content = new byte[] { 'a', 'b', 'c', 23, 42 };
+ FileSystemUtils.writeContent(originalFile, content);
+
+ try {
+ copyFile(originalFile, aDir);
+ fail();
+ } catch (IOException ex) {
+ assertEquals("error copying file: couldn't delete destination: "
+ + aDir + " (Directory not empty)",
+ ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testCopyTool() throws IOException {
+ createTestDirectoryTree();
+ Path originalFile = file1;
+ byte[] content = new byte[] { 'a', 'b', 'c', 23, 42 };
+ FileSystemUtils.writeContent(originalFile, content);
+
+ Path copyTarget = copyTool(topDir.getRelative("file-1"), aDir.getRelative("file-1"));
+
+ assertTrue(Arrays.equals(content, FileSystemUtils.readContent(copyTarget)));
+ assertEquals(file1.isWritable(), copyTarget.isWritable());
+ assertEquals(file1.isExecutable(), copyTarget.isExecutable());
+ assertEquals(file1.getLastModifiedTime(), copyTarget.getLastModifiedTime());
+ }
+
+ @Test
+ public void testCopyTreesBelow() throws IOException {
+ createTestDirectoryTree();
+ Path toPath = fileSystem.getPath("/copy-here");
+ toPath.createDirectory();
+
+ FileSystemUtils.copyTreesBelow(topDir, toPath);
+ checkTestDirectoryTreesBelow(toPath);
+ }
+
+ @Test
+ public void testCopyTreesBelowWithOverriding() throws IOException {
+ createTestDirectoryTree();
+ Path toPath = fileSystem.getPath("/copy-here");
+ toPath.createDirectory();
+ toPath.getChild("file-2");
+
+ FileSystemUtils.copyTreesBelow(topDir, toPath);
+ checkTestDirectoryTreesBelow(toPath);
+ }
+
+ @Test
+ public void testCopyTreesBelowToSubtree() throws IOException {
+ createTestDirectoryTree();
+ try {
+ FileSystemUtils.copyTreesBelow(topDir, aDir);
+ fail("Should not be able to copy a directory to a subdir");
+ } catch (IllegalArgumentException expected) {
+ assertEquals("/top-dir/a-dir is a subdirectory of /top-dir", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void testCopyFileAsDirectoryTree() throws IOException {
+ createTestDirectoryTree();
+ try {
+ FileSystemUtils.copyTreesBelow(file1, aDir);
+ fail("Should not be able to copy a file with copyDirectory method");
+ } catch (IOException expected) {
+ assertEquals("/top-dir/file-1 (Not a directory)", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void testCopyTreesBelowToFile() throws IOException {
+ createTestDirectoryTree();
+ Path copyDir = fileSystem.getPath("/my-dir");
+ Path copySubDir = fileSystem.getPath("/my-dir/subdir");
+ FileSystemUtils.createDirectoryAndParents(copySubDir);
+ try {
+ FileSystemUtils.copyTreesBelow(copyDir, file4);
+ fail("Should not be able to copy a directory to a file");
+ } catch (IOException expected) {
+ assertEquals("/file-4 (Not a directory)", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void testCopyTreesBelowFromUnexistingDir() throws IOException {
+ createTestDirectoryTree();
+
+ try {
+ Path unexistingDir = fileSystem.getPath("/unexisting-dir");
+ FileSystemUtils.copyTreesBelow(unexistingDir, aDir);
+ fail("Should not be able to copy from an unexisting path");
+ } catch (FileNotFoundException expected) {
+ assertEquals("/unexisting-dir (No such file or directory)", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void testTraverseTree() throws IOException {
+ createTestDirectoryTree();
+
+ Collection<Path> paths = traverseTree(topDir, new Predicate<Path>() {
+ @Override
+ public boolean apply(Path p) {
+ return !p.getPathString().contains("a-dir");
+ }
+ });
+ assertThat(paths).containsExactly(file1, file2);
+ }
+
+ @Test
+ public void testTraverseTreeDeep() throws IOException {
+ createTestDirectoryTree();
+
+ Collection<Path> paths = traverseTree(topDir,
+ Predicates.alwaysTrue());
+ assertThat(paths).containsExactly(aDir,
+ file3,
+ innerDir,
+ link1,
+ file1,
+ file2,
+ dirLink);
+ }
+
+ @Test
+ public void testTraverseTreeLinkDir() throws IOException {
+ // Use a new little tree for this test:
+ // top-dir/
+ // dir-link2 => linked-dir
+ // linked-dir/
+ // file
+ topDir = fileSystem.getPath("/top-dir");
+ Path dirLink2 = fileSystem.getPath("/top-dir/dir-link2");
+ Path linkedDir = fileSystem.getPath("/linked-dir");
+ Path linkedDirFile = fileSystem.getPath("/top-dir/dir-link2/file");
+
+ topDir.createDirectory();
+ linkedDir.createDirectory();
+ dirLink2.createSymbolicLink(linkedDir); // simple symlink
+ FileSystemUtils.createEmptyFile(linkedDirFile); // created through the link
+
+ // traverseTree doesn't follow links:
+ Collection<Path> paths = traverseTree(topDir, Predicates.alwaysTrue());
+ assertThat(paths).containsExactly(dirLink2);
+
+ paths = traverseTree(linkedDir, Predicates.alwaysTrue());
+ assertThat(paths).containsExactly(fileSystem.getPath("/linked-dir/file"));
+ }
+
+ @Test
+ public void testDeleteTreeCommandDeletesTree() throws IOException {
+ createTestDirectoryTree();
+ Path toDelete = topDir;
+ deleteTree(toDelete);
+
+ assertTrue(file4.exists());
+ assertFalse(topDir.exists());
+ assertFalse(file1.exists());
+ assertFalse(file2.exists());
+ assertFalse(aDir.exists());
+ assertFalse(file3.exists());
+ }
+
+ @Test
+ public void testDeleteTreeCommandsDeletesUnreadableDirectories() throws IOException {
+ createTestDirectoryTree();
+ Path toDelete = topDir;
+
+ try {
+ aDir.setReadable(false);
+ } catch (UnsupportedOperationException e) {
+ // For file systems that do not support setting readable attribute to
+ // false, this test is simply skipped.
+
+ return;
+ }
+
+ deleteTree(toDelete);
+ assertFalse(topDir.exists());
+ assertFalse(aDir.exists());
+
+ }
+
+ @Test
+ public void testDeleteTreeCommandDoesNotFollowLinksOut() throws IOException {
+ createTestDirectoryTree();
+ Path toDelete = topDir;
+ Path outboundLink = fileSystem.getPath("/top-dir/outbound-link");
+ outboundLink.createSymbolicLink(file4);
+
+ deleteTree(toDelete);
+
+ assertTrue(file4.exists());
+ assertFalse(topDir.exists());
+ assertFalse(file1.exists());
+ assertFalse(file2.exists());
+ assertFalse(aDir.exists());
+ assertFalse(file3.exists());
+ }
+
+ @Test
+ public void testDeleteTreesBelowNotPrefixed() throws IOException {
+ createTestDirectoryTree();
+ deleteTreesBelowNotPrefixed(topDir, new String[] { "file-"});
+ assertTrue(file1.exists());
+ assertTrue(file2.exists());
+ assertFalse(aDir.exists());
+ }
+
+ @Test
+ public void testCreateDirectories() throws IOException {
+ Path mainPath = fileSystem.getPath("/some/where/deep/in/the/hierarchy");
+ assertTrue(createDirectoryAndParents(mainPath));
+ assertTrue(mainPath.exists());
+ assertFalse(createDirectoryAndParents(mainPath));
+ }
+
+ @Test
+ public void testCreateDirectoriesWhenAncestorIsFile() throws IOException {
+ Path somewhereDeepIn = fileSystem.getPath("/somewhere/deep/in");
+ assertTrue(createDirectoryAndParents(somewhereDeepIn.getParentDirectory()));
+ FileSystemUtils.createEmptyFile(somewhereDeepIn);
+ Path theHierarchy = somewhereDeepIn.getChild("the-hierarchy");
+ try {
+ createDirectoryAndParents(theHierarchy);
+ fail();
+ } catch (IOException e) {
+ assertEquals("/somewhere/deep/in (Not a directory)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCreateDirectoriesWhenSymlinkToDir() throws IOException {
+ Path somewhereDeepIn = fileSystem.getPath("/somewhere/deep/in");
+ assertTrue(createDirectoryAndParents(somewhereDeepIn));
+ Path realDir = fileSystem.getPath("/real/dir");
+ assertTrue(createDirectoryAndParents(realDir));
+
+ Path theHierarchy = somewhereDeepIn.getChild("the-hierarchy");
+ theHierarchy.createSymbolicLink(realDir);
+
+ assertFalse(createDirectoryAndParents(theHierarchy));
+ }
+
+ @Test
+ public void testCreateDirectoriesWhenSymlinkEmbedded() throws IOException {
+ Path somewhereDeepIn = fileSystem.getPath("/somewhere/deep/in");
+ assertTrue(createDirectoryAndParents(somewhereDeepIn));
+ Path realDir = fileSystem.getPath("/real/dir");
+ assertTrue(createDirectoryAndParents(realDir));
+
+ Path the = somewhereDeepIn.getChild("the");
+ the.createSymbolicLink(realDir);
+
+ Path theHierarchy = somewhereDeepIn.getChild("hierarchy");
+ assertTrue(createDirectoryAndParents(theHierarchy));
+ }
+
+ PathFragment createPkg(Path rootA, Path rootB, String pkg) throws IOException {
+ if (rootA != null) {
+ createDirectoryAndParents(rootA.getRelative(pkg));
+ FileSystemUtils.createEmptyFile(rootA.getRelative(pkg).getChild("file"));
+ }
+ if (rootB != null) {
+ createDirectoryAndParents(rootB.getRelative(pkg));
+ FileSystemUtils.createEmptyFile(rootB.getRelative(pkg).getChild("file"));
+ }
+ return new PathFragment(pkg);
+ }
+
+ void assertLinksTo(Path fromRoot, Path toRoot, String relpart) throws IOException {
+ assertTrue(fromRoot.getRelative(relpart).isSymbolicLink());
+ assertEquals(toRoot.getRelative(relpart).asFragment(),
+ fromRoot.getRelative(relpart).readSymbolicLink());
+ }
+
+ void assertIsDir(Path root, String relpart) {
+ assertTrue(root.getRelative(relpart).isDirectory(Symlinks.NOFOLLOW));
+ }
+
+ void dumpTree(Path root, PrintStream out) throws IOException {
+ out.println("\n" + root);
+ for (Path p : FileSystemUtils.traverseTree(root, Predicates.alwaysTrue())) {
+ if (p.isDirectory(Symlinks.NOFOLLOW)) {
+ out.println(" " + p + "/");
+ } else if (p.isSymbolicLink()) {
+ out.println(" " + p + " => " + p.readSymbolicLink());
+ } else {
+ out.println(" " + p + " [" + p.resolveSymbolicLinks() + "]");
+ }
+ }
+ }
+
+ @Test
+ public void testPlantLinkForest() throws IOException {
+ Path rootA = fileSystem.getPath("/A");
+ Path rootB = fileSystem.getPath("/B");
+
+ ImmutableMap<PathFragment, Path> packageRootMap = ImmutableMap.<PathFragment, Path>builder()
+ .put(createPkg(rootA, rootB, "pkgA"), rootA)
+ .put(createPkg(rootA, rootB, "dir1/pkgA"), rootA)
+ .put(createPkg(rootA, rootB, "dir1/pkgB"), rootB)
+ .put(createPkg(rootA, rootB, "dir2/pkg"), rootA)
+ .put(createPkg(rootA, rootB, "dir2/pkg/pkg"), rootB)
+ .put(createPkg(rootA, rootB, "pkgB"), rootB)
+ .put(createPkg(rootA, rootB, "pkgB/dir/pkg"), rootA)
+ .put(createPkg(rootA, rootB, "pkgB/pkg"), rootA)
+ .put(createPkg(rootA, rootB, "pkgB/pkg/pkg"), rootA)
+ .build();
+ createPkg(rootA, rootB, "pkgB/dir"); // create a file in there
+
+ //dumpTree(rootA, System.err);
+ //dumpTree(rootB, System.err);
+
+ Path linkRoot = fileSystem.getPath("/linkRoot");
+ createDirectoryAndParents(linkRoot);
+ plantLinkForest(packageRootMap, linkRoot);
+
+ //dumpTree(linkRoot, System.err);
+
+ assertLinksTo(linkRoot, rootA, "pkgA");
+ assertIsDir(linkRoot, "dir1");
+ assertLinksTo(linkRoot, rootA, "dir1/pkgA");
+ assertLinksTo(linkRoot, rootB, "dir1/pkgB");
+ assertIsDir(linkRoot, "dir2");
+ assertIsDir(linkRoot, "dir2/pkg");
+ assertLinksTo(linkRoot, rootA, "dir2/pkg/file");
+ assertLinksTo(linkRoot, rootB, "dir2/pkg/pkg");
+ assertIsDir(linkRoot, "pkgB");
+ assertIsDir(linkRoot, "pkgB/dir");
+ assertLinksTo(linkRoot, rootB, "pkgB/dir/file");
+ assertLinksTo(linkRoot, rootA, "pkgB/dir/pkg");
+ assertLinksTo(linkRoot, rootA, "pkgB/pkg");
+ }
+
+ @Test
+ public void testWriteIsoLatin1() throws Exception {
+ Path file = fileSystem.getPath("/does/not/exist/yet.txt");
+ FileSystemUtils.writeIsoLatin1(file, "Line 1", "Line 2", "Line 3");
+ String expected = "Line 1\nLine 2\nLine 3\n";
+ String actual = new String(FileSystemUtils.readContentAsLatin1(file));
+ assertEquals(expected, actual);
+ }
+
+ @Test
+ public void testWriteLinesAs() throws Exception {
+ Path file = fileSystem.getPath("/does/not/exist/yet.txt");
+ FileSystemUtils.writeLinesAs(file, UTF_8, "\u00F6"); // an oe umlaut
+ byte[] expected = new byte[] {(byte) 0xC3, (byte) 0xB6, 0x0A};//"\u00F6\n";
+ byte[] actual = FileSystemUtils.readContent(file);
+ assertArrayEquals(expected, actual);
+ }
+
+ @Test
+ public void testGetFileSystem() throws Exception {
+ Path mountTable = fileSystem.getPath("/proc/mounts");
+ FileSystemUtils.writeIsoLatin1(mountTable,
+ "/dev/sda1 / ext2 blah 0 0",
+ "/dev/mapper/_dev_sda6 /usr/local/google ext3 blah 0 0",
+ "devshm /dev/shm tmpfs blah 0 0",
+ "/dev/fuse /fuse/mnt fuse blah 0 0",
+ "mtvhome22.nfs:/vol/mtvhome22/johndoe /home/johndoe nfs blah 0 0",
+ "/dev/foo /foo dummy_foo blah 0 0",
+ "/dev/foobar /foobar dummy_foobar blah 0 0",
+ "proc proc proc rw,noexec,nosuid,nodev 0 0");
+ Path path = fileSystem.getPath("/usr/local/google/_blaze");
+ FileSystemUtils.createDirectoryAndParents(path);
+ assertEquals("ext3", FileSystemUtils.getFileSystem(path));
+
+ // Should match the root "/"
+ path = fileSystem.getPath("/usr/local/tmp");
+ FileSystemUtils.createDirectoryAndParents(path);
+ assertEquals("ext2", FileSystemUtils.getFileSystem(path));
+
+ // Make sure we don't consider /foobar matches /foo
+ path = fileSystem.getPath("/foo");
+ FileSystemUtils.createDirectoryAndParents(path);
+ assertEquals("dummy_foo", FileSystemUtils.getFileSystem(path));
+ path = fileSystem.getPath("/foobar");
+ FileSystemUtils.createDirectoryAndParents(path);
+ assertEquals("dummy_foobar", FileSystemUtils.getFileSystem(path));
+
+ path = fileSystem.getPath("/dev/shm/blaze");
+ FileSystemUtils.createDirectoryAndParents(path);
+ assertEquals("tmpfs", FileSystemUtils.getFileSystem(path));
+
+ Path fusePath = fileSystem.getPath("/fuse/mnt/tmp");
+ FileSystemUtils.createDirectoryAndParents(fusePath);
+ assertEquals("fuse", FileSystemUtils.getFileSystem(fusePath));
+
+ // Create a symlink and make sure it gives the file system of the symlink target.
+ path = fileSystem.getPath("/usr/local/google/_blaze/out");
+ path.createSymbolicLink(fusePath);
+ assertEquals("fuse", FileSystemUtils.getFileSystem(path));
+
+ // Non existent path should return "unknown"
+ path = fileSystem.getPath("/does/not/exist");
+ assertEquals("unknown", FileSystemUtils.getFileSystem(path));
+ }
+
+ @Test
+ public void testStartsWithAnySuccess() throws Exception {
+ PathFragment a = new PathFragment("a");
+ assertTrue(FileSystemUtils.startsWithAny(a,
+ Arrays.asList(new PathFragment("b"), new PathFragment("a"))));
+ }
+
+ @Test
+ public void testStartsWithAnyNotFound() throws Exception {
+ PathFragment a = new PathFragment("a");
+ assertFalse(FileSystemUtils.startsWithAny(a,
+ Arrays.asList(new PathFragment("b"), new PathFragment("c"))));
+ }
+
+ @Test
+ public void testIterateLines() throws Exception {
+ Path file = fileSystem.getPath("/test.txt");
+ FileSystemUtils.writeContent(file, ISO_8859_1, "a\nb");
+ assertEquals(Arrays.asList("a", "b"),
+ Lists.newArrayList(FileSystemUtils.iterateLinesAsLatin1(file)));
+
+ FileSystemUtils.writeContent(file, ISO_8859_1, "a\rb");
+ assertEquals(Arrays.asList("a", "b"),
+ Lists.newArrayList(FileSystemUtils.iterateLinesAsLatin1(file)));
+
+ FileSystemUtils.writeContent(file, ISO_8859_1, "a\r\nb");
+ assertEquals(Arrays.asList("a", "b"),
+ Lists.newArrayList(FileSystemUtils.iterateLinesAsLatin1(file)));
+ }
+
+ @Test
+ public void testEnsureSymbolicLinkDoesNotMakeUnnecessaryChanges() throws Exception {
+ PathFragment target = new PathFragment("/b");
+ Path file = fileSystem.getPath("/a");
+ file.createSymbolicLink(target);
+ long prevTimeMillis = clock.currentTimeMillis();
+ clock.advanceMillis(1000);
+ FileSystemUtils.ensureSymbolicLink(file, target);
+ long timestamp = file.getLastModifiedTime(Symlinks.NOFOLLOW);
+ assertTrue(timestamp == prevTimeMillis);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/FileSystemsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemsTest.java
new file mode 100644
index 0000000000..88a000fea7
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/FileSystemsTest.java
@@ -0,0 +1,48 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+
+import com.google.devtools.build.lib.vfs.util.FileSystems;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This class handles the tests for the FileSystems class.
+ */
+@RunWith(JUnit4.class)
+public class FileSystemsTest {
+
+ @Test
+ public void testFileSystemsCreatesOnlyOneDefaultNative() {
+ assertSame(FileSystems.initDefaultAsNative(),
+ FileSystems.initDefaultAsNative());
+ }
+
+ @Test
+ public void testFileSystemsCreatesOnlyOneDefaultJavaIo() {
+ assertSame(FileSystems.initDefaultAsJavaIo(),
+ FileSystems.initDefaultAsJavaIo());
+ }
+
+ @Test
+ public void testFileSystemsCanSwitchDefaults() {
+ assertNotSame(FileSystems.initDefaultAsNative(),
+ FileSystems.initDefaultAsJavaIo());
+ }
+}
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
new file mode 100644
index 0000000000..37b7dc4957
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/GlobTest.java
@@ -0,0 +1,417 @@
+// Copyright 2014 Google Inc. 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests {@link UnixGlob}
+ */
+@RunWith(JUnit4.class)
+public class GlobTest {
+
+ private Path tmpPath;
+ private FileSystem fs;
+ @Before
+ public void setUp() throws Exception {
+ fs = new InMemoryFileSystem();
+ tmpPath = fs.getPath("/globtmp");
+ for (String dir : ImmutableList.of("foo/bar/wiz",
+ "foo/barnacle/wiz",
+ "food/barnacle/wiz",
+ "fool/barnacle/wiz")) {
+ FileSystemUtils.createDirectoryAndParents(tmpPath.getRelative(dir));
+ }
+ FileSystemUtils.createEmptyFile(tmpPath.getRelative("foo/bar/wiz/file"));
+ }
+
+ @Test
+ public void testQuestionMarkMatch() throws Exception {
+ assertGlobMatches("foo?", /* => */"food", "fool");
+ }
+
+ @Test
+ public void testQuestionMarkNoMatch() throws Exception {
+ assertGlobMatches("food/bar?" /* => nothing */);
+ }
+
+ @Test
+ public void testStartsWithStar() throws Exception {
+ assertGlobMatches("*oo", /* => */"foo");
+ }
+
+ @Test
+ public void testStartsWithStarWithMiddleStar() throws Exception {
+ assertGlobMatches("*f*o", /* => */"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 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 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 testGlobWithNonExistentBase() throws Exception {
+ Collection<Path> globResult = UnixGlob.forPath(fs.getPath("/does/not/exist"))
+ .addPattern("*.txt")
+ .globInterruptible();
+ assertEquals(0, globResult.size());
+ }
+
+ @Test
+ public void testGlobUnderFile() throws Exception {
+ assertGlobMatches("foo/bar/wiz/file/*" /* => nothing */);
+ }
+
+ @Test
+ public void testSingleFileExclude() throws Exception {
+ assertGlobWithExcludeMatches("*", "food", "foo", "fool");
+ }
+
+ @Test
+ public void testExcludeAll() throws Exception {
+ assertGlobWithExcludeMatches("*", "*");
+ }
+
+ @Test
+ public void testExcludeAllButNoMatches() throws Exception {
+ assertGlobWithExcludeMatches("not-there", "*");
+ }
+
+ @Test
+ public void testSingleFileExcludeDoesntMatch() throws Exception {
+ assertGlobWithExcludeMatches("food", "foo", "food");
+ }
+
+ @Test
+ public void testSingleFileExcludeForDirectoryWithChildGlob()
+ throws Exception {
+ assertGlobWithExcludeMatches("foo/*", "foo", "foo/bar", "foo/barnacle");
+ }
+
+ @Test
+ public void testChildGlobWithChildExclude()
+ throws Exception {
+ assertGlobWithExcludeMatches("foo/*", "foo/*");
+ assertGlobWithExcludeMatches("foo/bar", "foo/*");
+ assertGlobWithExcludeMatches("foo/bar", "foo/bar");
+ assertGlobWithExcludeMatches("foo/bar", "*/bar");
+ assertGlobWithExcludeMatches("foo/bar", "*/*");
+ assertGlobWithExcludeMatches("foo/bar/wiz", "*/*/*");
+ assertGlobWithExcludeMatches("foo/bar/wiz", "foo/*/*");
+ assertGlobWithExcludeMatches("foo/bar/wiz", "foo/bar/*");
+ assertGlobWithExcludeMatches("foo/bar/wiz", "foo/bar/wiz");
+ assertGlobWithExcludeMatches("foo/bar/wiz", "*/bar/wiz");
+ assertGlobWithExcludeMatches("foo/bar/wiz", "*/*/wiz");
+ assertGlobWithExcludeMatches("foo/bar/wiz", "foo/*/wiz");
+ }
+
+ private void assertGlobMatches(String pattern, String... expecteds)
+ throws Exception {
+ assertGlobWithExcludesMatches(
+ Collections.singleton(pattern), Collections.<String>emptyList(),
+ expecteds);
+ }
+
+ private void assertGlobMatches(Collection<String> pattern,
+ String... expecteds)
+ throws Exception {
+ assertGlobWithExcludesMatches(pattern, Collections.<String>emptyList(),
+ expecteds);
+ }
+
+ private void assertGlobWithExcludeMatches(String pattern, String exclude,
+ String... expecteds)
+ throws Exception {
+ assertGlobWithExcludesMatches(
+ Collections.singleton(pattern), Collections.singleton(exclude),
+ expecteds);
+ }
+
+ private void assertGlobWithExcludesMatches(Collection<String> pattern,
+ Collection<String> excludes,
+ String... expecteds)
+ throws Exception {
+ MoreAsserts.assertSameContents(resolvePaths(expecteds),
+ new UnixGlob.Builder(tmpPath)
+ .addPatterns(pattern)
+ .addExcludes(excludes)
+ .globInterruptible());
+ }
+
+ private Set<Path> resolvePaths(String... relativePaths) {
+ Set<Path> expectedFiles = new HashSet<>();
+ for (String expected : relativePaths) {
+ Path file = expected.equals(".")
+ ? tmpPath
+ : tmpPath.getRelative(expected);
+ expectedFiles.add(file);
+ }
+ return expectedFiles;
+ }
+
+ @Test
+ public void testGlobWithoutWildcardsDoesNotCallReaddir() throws Exception {
+ UnixGlob.FilesystemCalls syscalls = new UnixGlob.FilesystemCalls() {
+ @Override
+ public FileStatus statNullable(Path path, Symlinks symlinks) {
+ return UnixGlob.DEFAULT_SYSCALLS.statNullable(path, symlinks);
+ }
+
+ @Override
+ public Collection<Dirent> readdir(Path path, Symlinks symlinks) {
+ throw new IllegalStateException();
+ }
+ };
+
+ MoreAsserts.assertSameContents(ImmutableList.of(tmpPath.getRelative("foo/bar/wiz/file")),
+ new UnixGlob.Builder(tmpPath)
+ .addPattern("foo/bar/wiz/file")
+ .setFilesystemCalls(new AtomicReference<>(syscalls))
+ .glob());
+ }
+
+ @Test
+ public void testIllegalPatterns() throws Exception {
+ assertIllegalPattern("(illegal) pattern");
+ assertIllegalPattern("[illegal pattern");
+ assertIllegalPattern("}illegal pattern");
+ assertIllegalPattern("foo**bar");
+ assertIllegalPattern("");
+ assertIllegalPattern(".");
+ assertIllegalPattern("/foo");
+ assertIllegalPattern("./foo");
+ assertIllegalPattern("foo/");
+ assertIllegalPattern("foo/./bar");
+ assertIllegalPattern("../foo/bar");
+ assertIllegalPattern("foo//bar");
+ }
+
+ /**
+ * Tests that globs can contain Java regular expression special characters
+ */
+ @Test
+ public void testSpecialRegexCharacter() throws Exception {
+ Path tmpPath2 = fs.getPath("/globtmp2");
+ FileSystemUtils.createDirectoryAndParents(tmpPath2);
+ Path aDotB = tmpPath2.getChild("a.b");
+ FileSystemUtils.createEmptyFile(aDotB);
+ FileSystemUtils.createEmptyFile(tmpPath2.getChild("aab"));
+ // Note: this contains two asterisks because otherwise a RE is not built,
+ // as an optimization.
+ assertThat(UnixGlob.forPath(tmpPath2).addPattern("*a.b*").globInterruptible()).containsExactly(
+ aDotB);
+ }
+
+ @Test
+ public void testMatchesCallWithNoCache() {
+ assertTrue(UnixGlob.matches("*a*b", "CaCb", null));
+ }
+
+ @Test
+ public void testMultiplePatterns() throws Exception {
+ assertGlobMatches(Lists.newArrayList("foo", "fool"), "foo", "fool");
+ }
+
+ @Test
+ public void testMultiplePatternsWithExcludes() throws Exception {
+ assertGlobWithExcludesMatches(Lists.newArrayList("foo", "foo?"),
+ Lists.newArrayList("fool"), "foo", "food");
+ }
+
+ @Test
+ public void testMultiplePatternsWithOverlap() throws Exception {
+ assertGlobMatchesAnyOrder(Lists.newArrayList("food", "foo?"),
+ "food", "fool");
+ assertGlobMatchesAnyOrder(Lists.newArrayList("food", "?ood", "f??d"),
+ "food");
+ assertThat(resolvePaths("food", "fool", "foo")).containsExactlyElementsIn(
+ new UnixGlob.Builder(tmpPath).addPatterns("food", "xxx", "*").glob());
+
+ }
+
+ private void assertGlobMatchesAnyOrder(ArrayList<String> patterns,
+ String... paths) throws Exception {
+ assertThat(resolvePaths(paths)).containsExactlyElementsIn(
+ new UnixGlob.Builder(tmpPath).addPatterns(patterns).globInterruptible());
+ }
+
+ /**
+ * Tests that a glob returns files in sorted order.
+ */
+ @Test
+ public void testGlobEntriesAreSorted() throws Exception {
+ Collection<Path> directoryEntries = tmpPath.getDirectoryEntries();
+ List<Path> globResult = new UnixGlob.Builder(tmpPath)
+ .addPattern("*")
+ .setExcludeDirectories(false)
+ .globInterruptible();
+ assertThat(Ordering.natural().sortedCopy(directoryEntries)).containsExactlyElementsIn(
+ globResult).inOrder();
+ }
+
+ private void assertIllegalPattern(String pattern) throws Exception {
+ try {
+ new UnixGlob.Builder(tmpPath)
+ .addPattern(pattern)
+ .globInterruptible();
+ fail();
+ } catch (IllegalArgumentException e) {
+ MoreAsserts.assertContainsRegex("in glob pattern", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testHiddenFiles() throws Exception {
+ for (String dir : ImmutableList.of(".hidden", "..also.hidden", "not.hidden")) {
+ FileSystemUtils.createDirectoryAndParents(tmpPath.getRelative(dir));
+ }
+ // Note that these are not in the result: ".", ".."
+ assertGlobMatches("*", "not.hidden", "foo", "fool", "food", ".hidden", "..also.hidden");
+ assertGlobMatches("*.hidden", "not.hidden");
+ }
+
+ @Test
+ public void testCheckCanBeInterrupted() throws Exception {
+ final Thread mainThread = Thread.currentThread();
+ final ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
+
+ Predicate<Path> interrupterPredicate = new Predicate<Path>() {
+ @Override
+ public boolean apply(Path input) {
+ mainThread.interrupt();
+ return true;
+ }
+ };
+
+ try {
+ new UnixGlob.Builder(tmpPath)
+ .addPattern("**")
+ .setDirectoryFilter(interrupterPredicate)
+ .setThreadPool(executor)
+ .globInterruptible();
+ fail(); // Should have received InterruptedException
+ } catch (InterruptedException e) {
+ // good
+ }
+
+ assertFalse(executor.isShutdown());
+ executor.shutdown();
+ assertTrue(executor.awaitTermination(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testCheckCannotBeInterrupted() throws Exception {
+ final Thread mainThread = Thread.currentThread();
+ final ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
+ final AtomicBoolean sentInterrupt = new AtomicBoolean(false);
+
+ Predicate<Path> interrupterPredicate = new Predicate<Path>() {
+ @Override
+ public boolean apply(Path input) {
+ if (!sentInterrupt.getAndSet(true)) {
+ mainThread.interrupt();
+ }
+ return true;
+ }
+ };
+
+ List<Path> result = new UnixGlob.Builder(tmpPath)
+ .addPatterns("**", "*")
+ .setDirectoryFilter(interrupterPredicate).setThreadPool(executor).glob();
+
+ // In the non-interruptible case, the interrupt bit should be set, but the
+ // glob should return the correct set of full results.
+ assertTrue(Thread.interrupted());
+ MoreAsserts.assertSameContents(resolvePaths(".", "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"), result);
+
+ assertFalse(executor.isShutdown());
+ executor.shutdown();
+ assertTrue(executor.awaitTermination(TestUtils.WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java
new file mode 100644
index 0000000000..fdb6283d20
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/JavaIoFileSystemTest.java
@@ -0,0 +1,40 @@
+// Copyright 2014 Google Inc. 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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link JavaIoFileSystem}. That file system by itself is not
+ * capable of creating symlinks; use the unix one to create them, so that the
+ * test can check that the file system handles their existence correctly.
+ */
+@RunWith(JUnit4.class)
+public class JavaIoFileSystemTest extends SymlinkAwareFileSystemTest {
+
+ @Override
+ public FileSystem getFreshFileSystem() {
+ return new JavaIoFileSystem();
+ }
+
+ // The tests are just inherited from the FileSystemTest
+
+ // JavaIoFileSystem incorrectly throws a FileNotFoundException for all IO errors. This means that
+ // statIfFound incorrectly suppresses those errors.
+ @Override
+ @Test
+ public void testBadPermissionsThrowsExceptionOnStatIfFound() {}
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/ModifiedFileSetTest.java b/src/test/java/com/google/devtools/build/lib/vfs/ModifiedFileSetTest.java
new file mode 100644
index 0000000000..96001dfa2f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/ModifiedFileSetTest.java
@@ -0,0 +1,54 @@
+// Copyright 2014 Google Inc. 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 com.google.common.collect.ImmutableList;
+import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link ModifiedFileSet}.
+ */
+@RunWith(JUnit4.class)
+public class ModifiedFileSetTest {
+
+ @Test
+ public void testHashCodeAndEqualsContract() throws Exception {
+ PathFragment fragA = new PathFragment("a");
+ PathFragment fragB = new PathFragment("b");
+
+ ModifiedFileSet empty1 = ModifiedFileSet.NOTHING_MODIFIED;
+ ModifiedFileSet empty2 = ModifiedFileSet.builder().build();
+ ModifiedFileSet empty3 = ModifiedFileSet.builder().modifyAll(
+ ImmutableList.<PathFragment>of()).build();
+
+ ModifiedFileSet nonEmpty1 = ModifiedFileSet.builder().modifyAll(
+ ImmutableList.of(fragA, fragB)).build();
+ ModifiedFileSet nonEmpty2 = ModifiedFileSet.builder().modifyAll(
+ ImmutableList.of(fragB, fragA)).build();
+ ModifiedFileSet nonEmpty3 = ModifiedFileSet.builder().modify(fragA).modify(fragB).build();
+ ModifiedFileSet nonEmpty4 = ModifiedFileSet.builder().modify(fragB).modify(fragA).build();
+
+ ModifiedFileSet everythingModified = ModifiedFileSet.EVERYTHING_MODIFIED;
+
+ new EqualsTester()
+ .addEqualityGroup(empty1, empty2, empty3)
+ .addEqualityGroup(nonEmpty1, nonEmpty2, nonEmpty3, nonEmpty4)
+ .addEqualityGroup(everythingModified)
+ .testEquals();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
new file mode 100644
index 0000000000..9ab9bfa9f3
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentTest.java
@@ -0,0 +1,481 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class tests the functionality of the PathFragment.
+ */
+@RunWith(JUnit4.class)
+public class PathFragmentTest {
+ @Test
+ public void testMergeFourPathsWithAbsolute() {
+ assertEquals(new PathFragment("x/y/z/a/b/c/d/e"),
+ new PathFragment(new PathFragment("x/y"), new PathFragment("z/a"),
+ new PathFragment("/b/c"), // absolute!
+ new PathFragment("d/e")));
+ }
+
+ @Test
+ public void testEqualsAndHashCode() {
+ InMemoryFileSystem filesystem = new InMemoryFileSystem();
+
+ new EqualsTester()
+ .addEqualityGroup(new PathFragment("../relative/path"),
+ new PathFragment("../relative/path"),
+ new PathFragment(new File("../relative/path")))
+ .addEqualityGroup(new PathFragment("something/else"))
+ .addEqualityGroup(new PathFragment("/something/else"))
+ .addEqualityGroup(new PathFragment("/"),
+ new PathFragment("//////"))
+ .addEqualityGroup(new PathFragment(""))
+ .addEqualityGroup(filesystem.getRootDirectory()) // A Path object.
+ .testEquals();
+ }
+
+ @Test
+ public void testHashCodeCache() {
+ PathFragment relativePath = new PathFragment("../relative/path");
+ PathFragment rootPath = new PathFragment("/");
+
+ int oldResult = relativePath.hashCode();
+ int rootResult = rootPath.hashCode();
+ assertEquals(oldResult, relativePath.hashCode());
+ assertEquals(rootResult, rootPath.hashCode());
+ }
+
+ private void checkRelativeTo(String path, String base) {
+ PathFragment relative = new PathFragment(path).relativeTo(base);
+ assertEquals(new PathFragment(path), new PathFragment(base).getRelative(relative).normalize());
+ }
+
+ @Test
+ public void testRelativeTo() {
+ assertPath("bar/baz", new PathFragment("foo/bar/baz").relativeTo("foo"));
+ assertPath("bar/baz", new PathFragment("/foo/bar/baz").relativeTo("/foo"));
+ assertPath("baz", new PathFragment("foo/bar/baz").relativeTo("foo/bar"));
+ assertPath("baz", new PathFragment("/foo/bar/baz").relativeTo("/foo/bar"));
+ assertPath("foo", new PathFragment("/foo").relativeTo("/"));
+ assertPath("foo", new PathFragment("foo").relativeTo(""));
+ assertPath("foo/bar", new PathFragment("foo/bar").relativeTo(""));
+
+ checkRelativeTo("foo/bar/baz", "foo");
+ checkRelativeTo("/foo/bar/baz", "/foo");
+ checkRelativeTo("foo/bar/baz", "foo/bar");
+ checkRelativeTo("/foo/bar/baz", "/foo/bar");
+ checkRelativeTo("/foo", "/");
+ checkRelativeTo("foo", "");
+ checkRelativeTo("foo/bar", "");
+ }
+
+ @Test
+ public void testIsAbsolute() {
+ assertTrue(new PathFragment("/absolute/test").isAbsolute());
+ assertFalse(new PathFragment("relative/test").isAbsolute());
+ assertTrue(new PathFragment(new File("/absolute/test")).isAbsolute());
+ assertFalse(new PathFragment(new File("relative/test")).isAbsolute());
+ }
+
+ @Test
+ public void testIsNormalized() {
+ assertTrue(new PathFragment("/absolute/path").isNormalized());
+ assertTrue(new PathFragment("some//path").isNormalized());
+ assertFalse(new PathFragment("some/./path").isNormalized());
+ assertFalse(new PathFragment("../some/path").isNormalized());
+ assertFalse(new PathFragment("some/other/../path").isNormalized());
+ assertTrue(new PathFragment("some/other//tricky..path..").isNormalized());
+ assertTrue(new PathFragment("/some/other//tricky..path..").isNormalized());
+ }
+
+ @Test
+ public void testRootNodeReturnsRootString() {
+ PathFragment rootFragment = new PathFragment("/");
+ assertEquals("/", rootFragment.getPathString());
+ }
+
+ @Test
+ public void testGetPathFragmentDoesNotNormalize() {
+ String nonCanonicalPath = "/a/weird/noncanonical/../path/.";
+ assertEquals(nonCanonicalPath,
+ new PathFragment(nonCanonicalPath).getPathString());
+ }
+
+ @Test
+ public void testGetRelative() {
+ assertEquals("a/b", new PathFragment("a").getRelative("b").getPathString());
+ assertEquals("a/b/c/d", new PathFragment("a/b").getRelative("c/d").getPathString());
+ assertEquals("/a/b", new PathFragment("c/d").getRelative("/a/b").getPathString());
+ assertEquals("a", new PathFragment("a").getRelative("").getPathString());
+ assertEquals("/", new PathFragment("/").getRelative("").getPathString());
+ }
+
+ @Test
+ public void testGetChildWorks() {
+ PathFragment pf = new PathFragment("../some/path");
+ assertEquals(new PathFragment("../some/path/hi"), pf.getChild("hi"));
+ }
+
+ @Test
+ public void testGetChildRejectsInvalidBaseNames() {
+ PathFragment pf = new PathFragment("../some/path");
+ assertGetChildFails(pf, ".");
+ assertGetChildFails(pf, "..");
+ assertGetChildFails(pf, "x/y");
+ assertGetChildFails(pf, "/y");
+ assertGetChildFails(pf, "y/");
+ assertGetChildFails(pf, "");
+ }
+
+ private void assertGetChildFails(PathFragment pf, String baseName) {
+ try {
+ pf.getChild(baseName);
+ fail();
+ } catch (Exception e) { /* Expected. */ }
+ }
+
+ // Tests after here test the canonicalization
+ private void assertRegular(String expected, String actual) {
+ assertEquals(expected, new PathFragment(actual).getPathString()); // compare string forms
+ assertEquals(new PathFragment(expected), new PathFragment(actual)); // compare fragment forms
+ }
+
+ @Test
+ public void testEmptyPathToEmptyPath() {
+ assertRegular("/", "/");
+ assertRegular("", "");
+ }
+
+ @Test
+ public void testRedundantSlashes() {
+ assertRegular("/", "///");
+ assertRegular("/foo/bar", "/foo///bar");
+ assertRegular("/foo/bar", "////foo//bar");
+ }
+
+ @Test
+ public void testSimpleNameToSimpleName() {
+ assertRegular("/foo", "/foo");
+ assertRegular("foo", "foo");
+ }
+
+ @Test
+ public void testSimplePathToSimplePath() {
+ assertRegular("/foo/bar", "/foo/bar");
+ assertRegular("foo/bar", "foo/bar");
+ }
+
+ @Test
+ public void testStripsTrailingSlash() {
+ assertRegular("/foo/bar", "/foo/bar/");
+ }
+
+ @Test
+ public void testGetParentDirectory() {
+ PathFragment fooBarWiz = new PathFragment("foo/bar/wiz");
+ PathFragment fooBar = new PathFragment("foo/bar");
+ PathFragment foo = new PathFragment("foo");
+ PathFragment empty = new PathFragment("");
+ assertEquals(fooBar, fooBarWiz.getParentDirectory());
+ assertEquals(foo, fooBar.getParentDirectory());
+ assertEquals(empty, foo.getParentDirectory());
+ assertNull(empty.getParentDirectory());
+
+ PathFragment fooBarWizAbs = new PathFragment("/foo/bar/wiz");
+ PathFragment fooBarAbs = new PathFragment("/foo/bar");
+ PathFragment fooAbs = new PathFragment("/foo");
+ PathFragment rootAbs = new PathFragment("/");
+ assertEquals(fooBarAbs, fooBarWizAbs.getParentDirectory());
+ assertEquals(fooAbs, fooBarAbs.getParentDirectory());
+ assertEquals(rootAbs, fooAbs.getParentDirectory());
+ assertNull(rootAbs.getParentDirectory());
+
+ // Note, this is surprising but correct behavior:
+ assertEquals(fooBarAbs,
+ new PathFragment("/foo/bar/..").getParentDirectory());
+ }
+
+ @Test
+ public void testSegmentsCount() {
+ assertEquals(2, new PathFragment("foo/bar").segmentCount());
+ assertEquals(2, new PathFragment("/foo/bar").segmentCount());
+ assertEquals(2, new PathFragment("foo//bar").segmentCount());
+ assertEquals(2, new PathFragment("/foo//bar").segmentCount());
+ assertEquals(1, new PathFragment("foo/").segmentCount());
+ assertEquals(1, new PathFragment("/foo/").segmentCount());
+ assertEquals(1, new PathFragment("foo").segmentCount());
+ assertEquals(1, new PathFragment("/foo").segmentCount());
+ assertEquals(0, new PathFragment("/").segmentCount());
+ assertEquals(0, new PathFragment("").segmentCount());
+ }
+
+
+ @Test
+ public void testGetSegment() {
+ assertEquals("foo", new PathFragment("foo/bar").getSegment(0));
+ assertEquals("bar", new PathFragment("foo/bar").getSegment(1));
+ assertEquals("foo", new PathFragment("/foo/bar").getSegment(0));
+ assertEquals("bar", new PathFragment("/foo/bar").getSegment(1));
+ assertEquals("foo", new PathFragment("foo/").getSegment(0));
+ assertEquals("foo", new PathFragment("/foo/").getSegment(0));
+ assertEquals("foo", new PathFragment("foo").getSegment(0));
+ assertEquals("foo", new PathFragment("/foo").getSegment(0));
+ }
+
+ @Test
+ public void testBasename() throws Exception {
+ assertEquals("bar", new PathFragment("foo/bar").getBaseName());
+ assertEquals("bar", new PathFragment("/foo/bar").getBaseName());
+ assertEquals("foo", new PathFragment("foo/").getBaseName());
+ assertEquals("foo", new PathFragment("/foo/").getBaseName());
+ assertEquals("foo", new PathFragment("foo").getBaseName());
+ assertEquals("foo", new PathFragment("/foo").getBaseName());
+ assertEquals("", new PathFragment("/").getBaseName());
+ assertEquals("", new PathFragment("").getBaseName());
+ }
+
+ private static void assertPath(String expected, PathFragment actual) {
+ assertEquals(expected, actual.getPathString());
+ }
+
+ @Test
+ public void testReplaceName() throws Exception {
+ assertPath("foo/baz", new PathFragment("foo/bar").replaceName("baz"));
+ assertPath("/foo/baz", new PathFragment("/foo/bar").replaceName("baz"));
+ assertPath("foo", new PathFragment("foo/bar").replaceName(""));
+ assertPath("baz", new PathFragment("foo/").replaceName("baz"));
+ assertPath("/baz", new PathFragment("/foo/").replaceName("baz"));
+ assertPath("baz", new PathFragment("foo").replaceName("baz"));
+ assertPath("/baz", new PathFragment("/foo").replaceName("baz"));
+ assertEquals(null, new PathFragment("/").replaceName("baz"));
+ assertEquals(null, new PathFragment("/").replaceName(""));
+ assertEquals(null, new PathFragment("").replaceName("baz"));
+ assertEquals(null, new PathFragment("").replaceName(""));
+
+ assertPath("foo/bar/baz", new PathFragment("foo/bar").replaceName("bar/baz"));
+ assertPath("foo/bar/baz", new PathFragment("foo/bar").replaceName("bar/baz/"));
+
+ // Absolute path arguments will clobber the original path.
+ assertPath("/absolute", new PathFragment("foo/bar").replaceName("/absolute"));
+ assertPath("/", new PathFragment("foo/bar").replaceName("/"));
+ }
+ @Test
+ public void testSubFragment() throws Exception {
+ assertPath("/foo/bar/baz",
+ new PathFragment("/foo/bar/baz").subFragment(0, 3));
+ assertPath("foo/bar/baz",
+ new PathFragment("foo/bar/baz").subFragment(0, 3));
+ assertPath("/foo/bar",
+ new PathFragment("/foo/bar/baz").subFragment(0, 2));
+ assertPath("bar/baz",
+ new PathFragment("/foo/bar/baz").subFragment(1, 3));
+ assertPath("/foo",
+ new PathFragment("/foo/bar/baz").subFragment(0, 1));
+ assertPath("bar",
+ new PathFragment("/foo/bar/baz").subFragment(1, 2));
+ assertPath("baz", new PathFragment("/foo/bar/baz").subFragment(2, 3));
+ assertPath("/", new PathFragment("/foo/bar/baz").subFragment(0, 0));
+ assertPath("", new PathFragment("foo/bar/baz").subFragment(0, 0));
+ assertPath("", new PathFragment("foo/bar/baz").subFragment(1, 1));
+ try {
+ fail("unexpectedly succeeded: " + new PathFragment("foo/bar/baz").subFragment(3, 2));
+ } catch (IndexOutOfBoundsException e) { /* Expected. */ }
+ try {
+ fail("unexpectedly succeeded: " + new PathFragment("foo/bar/baz").subFragment(4, 4));
+ } catch (IndexOutOfBoundsException e) { /* Expected. */ }
+ }
+
+ @Test
+ public void testStartsWith() {
+ PathFragment foobar = new PathFragment("/foo/bar");
+ PathFragment foobarRelative = new PathFragment("foo/bar");
+
+ // (path, prefix) => true
+ assertTrue(foobar.startsWith(foobar));
+ assertTrue(foobar.startsWith(new PathFragment("/")));
+ assertTrue(foobar.startsWith(new PathFragment("/foo")));
+ assertTrue(foobar.startsWith(new PathFragment("/foo/")));
+ assertTrue(foobar.startsWith(new PathFragment("/foo/bar/"))); // Includes trailing slash.
+
+ // (prefix, path) => false
+ assertFalse(new PathFragment("/foo").startsWith(foobar));
+ assertFalse(new PathFragment("/").startsWith(foobar));
+
+ // (absolute, relative) => false
+ assertFalse(foobar.startsWith(foobarRelative));
+ assertFalse(foobarRelative.startsWith(foobar));
+
+ // (relative path, relative prefix) => true
+ assertTrue(foobarRelative.startsWith(foobarRelative));
+ assertTrue(foobarRelative.startsWith(new PathFragment("foo")));
+ assertTrue(foobarRelative.startsWith(new PathFragment("")));
+
+ // (path, sibling) => false
+ assertFalse(new PathFragment("/foo/wiz").startsWith(foobar));
+ assertFalse(foobar.startsWith(new PathFragment("/foo/wiz")));
+
+ // Does not normalize.
+ PathFragment foodotbar = new PathFragment("foo/./bar");
+ assertTrue(foodotbar.startsWith(foodotbar));
+ assertTrue(foodotbar.startsWith(new PathFragment("foo/.")));
+ assertTrue(foodotbar.startsWith(new PathFragment("foo/./")));
+ assertTrue(foodotbar.startsWith(new PathFragment("foo/./bar")));
+ assertFalse(foodotbar.startsWith(new PathFragment("foo/bar")));
+ }
+
+ @Test
+ public void testEndsWith() {
+ PathFragment foobar = new PathFragment("/foo/bar");
+ PathFragment foobarRelative = new PathFragment("foo/bar");
+
+ // (path, suffix) => true
+ assertTrue(foobar.endsWith(foobar));
+ assertTrue(foobar.endsWith(new PathFragment("bar")));
+ assertTrue(foobar.endsWith(new PathFragment("foo/bar")));
+ assertTrue(foobar.endsWith(new PathFragment("/foo/bar")));
+ assertFalse(foobar.endsWith(new PathFragment("/bar")));
+
+ // (prefix, path) => false
+ assertFalse(new PathFragment("/foo").endsWith(foobar));
+ assertFalse(new PathFragment("/").endsWith(foobar));
+
+ // (suffix, path) => false
+ assertFalse(new PathFragment("/bar").endsWith(foobar));
+ assertFalse(new PathFragment("bar").endsWith(foobar));
+ assertFalse(new PathFragment("").endsWith(foobar));
+
+ // (absolute, relative) => true
+ assertTrue(foobar.endsWith(foobarRelative));
+
+ // (relative, absolute) => false
+ assertFalse(foobarRelative.endsWith(foobar));
+
+ // (relative path, relative prefix) => true
+ assertTrue(foobarRelative.endsWith(foobarRelative));
+ assertTrue(foobarRelative.endsWith(new PathFragment("bar")));
+ assertTrue(foobarRelative.endsWith(new PathFragment("")));
+
+ // (path, sibling) => false
+ assertFalse(new PathFragment("/foo/wiz").endsWith(foobar));
+ assertFalse(foobar.endsWith(new PathFragment("/foo/wiz")));
+ }
+
+ static List<PathFragment> toPaths(List<String> strs) {
+ List<PathFragment> paths = Lists.newArrayList();
+ for (String s : strs) {
+ paths.add(new PathFragment(s));
+ }
+ return paths;
+ }
+
+ @Test
+ public void testCompareTo() throws Exception {
+ List<String> pathStrs = ImmutableList.of(
+ "", "/", "//", ".", "/./", "foo/.//bar", "foo", "/foo", "foo/bar", "foo/Bar", "Foo/bar");
+ List<PathFragment> paths = toPaths(pathStrs);
+ // First test that compareTo is self-consistent.
+ for (PathFragment x : paths) {
+ for (PathFragment y : paths) {
+ for (PathFragment z : paths) {
+ // Anti-symmetry
+ assertEquals(Integer.signum(x.compareTo(y)),
+ -1 * Integer.signum(y.compareTo(x)));
+ // Transitivity
+ if (x.compareTo(y) > 0 && y.compareTo(z) > 0) {
+ MoreAsserts.assertGreaterThan(0, x.compareTo(z));
+ }
+ // "Substitutability"
+ if (x.compareTo(y) == 0) {
+ assertEquals(Integer.signum(x.compareTo(z)), Integer.signum(y.compareTo(z)));
+ }
+ // Consistency with equals
+ assertEquals((x.compareTo(y) == 0), x.equals(y));
+ }
+ }
+ }
+ // Now test that compareTo does what we expect. The exact ordering here doesn't matter much,
+ // but there are three things to notice: 1. absolute < relative, 2. comparison is lexicographic
+ // 3. repeated slashes are ignored. (PathFragment("//") prints as "/").
+ Collections.shuffle(paths);
+ Collections.sort(paths);
+ List<PathFragment> expectedOrder = toPaths(ImmutableList.of(
+ "/", "//", "/./", "/foo", "", ".", "Foo/bar", "foo", "foo/.//bar", "foo/Bar", "foo/bar"));
+ assertEquals(expectedOrder, paths);
+ }
+
+ @Test
+ public void testGetSafePathString() {
+ assertEquals("/", new PathFragment("/").getSafePathString());
+ assertEquals("/abc", new PathFragment("/abc").getSafePathString());
+ assertEquals(".", new PathFragment("").getSafePathString());
+ assertEquals(".", PathFragment.EMPTY_FRAGMENT.getSafePathString());
+ assertEquals("abc/def", new PathFragment("abc/def").getSafePathString());
+ }
+
+ @Test
+ public void testNormalize() {
+ assertEquals(new PathFragment("/a/b"), new PathFragment("/a/b").normalize());
+ assertEquals(new PathFragment("/a/b"), new PathFragment("/a/./b").normalize());
+ assertEquals(new PathFragment("/b"), new PathFragment("/a/../b").normalize());
+ assertEquals(new PathFragment("a/b"), new PathFragment("a/b").normalize());
+ assertEquals(new PathFragment("../b"), new PathFragment("a/../../b").normalize());
+ assertEquals(new PathFragment(".."), new PathFragment("a/../..").normalize());
+ assertEquals(new PathFragment("b"), new PathFragment("a/../b").normalize());
+ assertEquals(new PathFragment("a/b"), new PathFragment("a/b/../b").normalize());
+ assertEquals(new PathFragment("/.."), new PathFragment("/..").normalize());
+ }
+
+ @Test
+ public void testSerializationSimple() throws Exception {
+ checkSerialization("a", 91);
+ }
+
+ @Test
+ public void testSerializationAbsolute() throws Exception {
+ checkSerialization("/foo", 94);
+ }
+
+ @Test
+ public void testSerializationNested() throws Exception {
+ checkSerialization("foo/bar/baz", 101);
+ }
+
+ private void checkSerialization(String pathFragmentString, int expectedSize) throws Exception {
+ PathFragment a = new PathFragment(pathFragmentString);
+ byte[] sa = TestUtils.serializeObject(a);
+ assertEquals(expectedSize, sa.length);
+
+ PathFragment a2 = (PathFragment) TestUtils.deserializeObject(sa);
+ assertEquals(a, a2);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
new file mode 100644
index 0000000000..43c94d4b8f
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathFragmentWindowsTest.java
@@ -0,0 +1,218 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+/**
+ * This class tests the functionality of the PathFragment.
+ */
+@RunWith(JUnit4.class)
+public class PathFragmentWindowsTest {
+
+ @Test
+ public void testWindowsSeparator() {
+ assertEquals("bar/baz", new PathFragment("bar\\baz").toString());
+ assertEquals("C:/bar/baz", new PathFragment("c:\\bar\\baz").toString());
+ }
+
+ @Test
+ public void testIsAbsoluteWindows() {
+ assertTrue(new PathFragment("C:/").isAbsolute());
+ assertTrue(new PathFragment("C:/").isAbsolute());
+ assertTrue(new PathFragment("C:/foo").isAbsolute());
+ assertTrue(new PathFragment("d:/foo/bar").isAbsolute());
+
+ assertFalse(new PathFragment("*:/").isAbsolute());
+
+ // C: is not an absolute path, it points to the current active directory on drive C:.
+ assertFalse(new PathFragment("C:").isAbsolute());
+ assertFalse(new PathFragment("C:foo").isAbsolute());
+ }
+
+ @Test
+ public void testIsAbsoluteWindowsBackslash() {
+ assertTrue(new PathFragment(new File("C:\\blah")).isAbsolute());
+ assertTrue(new PathFragment(new File("C:\\")).isAbsolute());
+ assertTrue(new PathFragment(new File("\\blah")).isAbsolute());
+ assertTrue(new PathFragment(new File("\\")).isAbsolute());
+ }
+
+ @Test
+ public void testIsNormalizedWindows() {
+ assertTrue(new PathFragment("C:/").isNormalized());
+ assertTrue(new PathFragment("C:/absolute/path").isNormalized());
+ assertFalse(new PathFragment("C:/absolute/./path").isNormalized());
+ assertFalse(new PathFragment("C:/absolute/../path").isNormalized());
+ }
+
+ @Test
+ public void testRootNodeReturnsRootStringWindows() {
+ PathFragment rootFragment = new PathFragment("C:/");
+ assertEquals("C:/", rootFragment.getPathString());
+ }
+
+ @Test
+ public void testGetRelativeWindows() {
+ assertEquals("C:/a/b", new PathFragment("C:/a").getRelative("b").getPathString());
+ assertEquals("C:/a/b/c/d", new PathFragment("C:/a/b").getRelative("c/d").getPathString());
+ assertEquals("C:/b", new PathFragment("C:/a").getRelative("C:/b").getPathString());
+ assertEquals("C:/c/d", new PathFragment("C:/a/b").getRelative("C:/c/d").getPathString());
+ assertEquals("C:/b", new PathFragment("a").getRelative("C:/b").getPathString());
+ assertEquals("C:/c/d", new PathFragment("a/b").getRelative("C:/c/d").getPathString());
+ }
+
+ @Test
+ public void testGetRelativeMixed() {
+ assertEquals("/b", new PathFragment("C:/a").getRelative("/b").getPathString());
+ assertEquals("C:/b", new PathFragment("/a").getRelative("C:/b").getPathString());
+ }
+
+ @Test
+ public void testGetChildWorks() {
+ PathFragment pf = new PathFragment("../some/path");
+ assertEquals(new PathFragment("../some/path/hi"), pf.getChild("hi"));
+ }
+
+ // Tests after here test the canonicalization
+ private void assertRegular(String expected, String actual) {
+ assertEquals(expected, new PathFragment(actual).getPathString()); // compare string forms
+ assertEquals(new PathFragment(expected), new PathFragment(actual)); // compare fragment forms
+ }
+
+ @Test
+ public void testEmptyPathToEmptyPathWindows() {
+ assertRegular("C:/", "C:/");
+ }
+
+ @Test
+ public void testEmptyRelativePathToEmptyPathWindows() {
+ assertRegular("C:", "C:");
+ }
+
+ @Test
+ public void testWindowsVolumeUppercase() {
+ assertRegular("C:/", "c:/");
+ }
+
+ @Test
+ public void testRedundantSlashesWindows() {
+ assertRegular("C:/", "C:///");
+ assertRegular("C:/foo/bar", "C:/foo///bar");
+ assertRegular("C:/foo/bar", "C:////foo//bar");
+ }
+
+ @Test
+ public void testSimpleNameToSimpleNameWindows() {
+ assertRegular("C:/foo", "C:/foo");
+ }
+
+ @Test
+ public void testStripsTrailingSlashWindows() {
+ assertRegular("C:/foo/bar", "C:/foo/bar/");
+ }
+
+ @Test
+ public void testGetParentDirectoryWindows() {
+ PathFragment fooBarWizAbs = new PathFragment("C:/foo/bar/wiz");
+ PathFragment fooBarAbs = new PathFragment("C:/foo/bar");
+ PathFragment fooAbs = new PathFragment("C:/foo");
+ PathFragment rootAbs = new PathFragment("C:/");
+ assertEquals(fooBarAbs, fooBarWizAbs.getParentDirectory());
+ assertEquals(fooAbs, fooBarAbs.getParentDirectory());
+ assertEquals(rootAbs, fooAbs.getParentDirectory());
+ assertNull(rootAbs.getParentDirectory());
+
+ // Note, this is suprising but correct behaviour:
+ assertEquals(fooBarAbs,
+ new PathFragment("C:/foo/bar/..").getParentDirectory());
+ }
+
+ @Test
+ public void testSegmentsCountWindows() {
+ assertEquals(1, new PathFragment("C:/foo").segmentCount());
+ assertEquals(0, new PathFragment("C:/").segmentCount());
+ }
+
+ @Test
+ public void testGetSegmentWindows() {
+ assertEquals("foo", new PathFragment("C:/foo/bar").getSegment(0));
+ assertEquals("bar", new PathFragment("C:/foo/bar").getSegment(1));
+ assertEquals("foo", new PathFragment("C:/foo/").getSegment(0));
+ assertEquals("foo", new PathFragment("C:/foo").getSegment(0));
+ }
+
+ @Test
+ public void testBasenameWindows() throws Exception {
+ assertEquals("bar", new PathFragment("C:/foo/bar").getBaseName());
+ assertEquals("foo", new PathFragment("C:/foo").getBaseName());
+ // Never return the drive name as a basename.
+ assertEquals("", new PathFragment("C:/").getBaseName());
+ }
+
+ private static void assertPath(String expected, PathFragment actual) {
+ assertEquals(expected, actual.getPathString());
+ }
+
+ @Test
+ public void testReplaceNameWindows() throws Exception {
+ assertPath("C:/foo/baz", new PathFragment("C:/foo/bar").replaceName("baz"));
+ assertEquals(null, new PathFragment("C:/").replaceName("baz"));
+ }
+
+ @Test
+ public void testStartsWithWindows() {
+ assertTrue(new PathFragment("C:/foo/bar").startsWith(new PathFragment("C:/foo")));
+ assertTrue(new PathFragment("C:/foo/bar").startsWith(new PathFragment("C:/")));
+ assertTrue(new PathFragment("C:foo/bar").startsWith(new PathFragment("C:")));
+ assertTrue(new PathFragment("C:/").startsWith(new PathFragment("C:/")));
+ assertTrue(new PathFragment("C:").startsWith(new PathFragment("C:")));
+
+ // The first path is absolute, the second is not.
+ assertFalse(new PathFragment("C:/foo/bar").startsWith(new PathFragment("C:")));
+ assertFalse(new PathFragment("C:/").startsWith(new PathFragment("C:")));
+ }
+
+ @Test
+ public void testEndsWithWindows() {
+ assertTrue(new PathFragment("C:/foo/bar").endsWith(new PathFragment("bar")));
+ assertTrue(new PathFragment("C:/foo/bar").endsWith(new PathFragment("foo/bar")));
+ assertTrue(new PathFragment("C:/foo/bar").endsWith(new PathFragment("C:/foo/bar")));
+ assertTrue(new PathFragment("C:/").endsWith(new PathFragment("C:/")));
+ }
+
+ @Test
+ public void testGetSafePathStringWindows() {
+ assertEquals("C:/", new PathFragment("C:/").getSafePathString());
+ assertEquals("C:/abc", new PathFragment("C:/abc").getSafePathString());
+ assertEquals("C:/abc/def", new PathFragment("C:/abc/def").getSafePathString());
+ }
+
+ @Test
+ public void testNormalizeWindows() {
+ assertEquals(new PathFragment("C:/a/b"), new PathFragment("C:/a/b").normalize());
+ assertEquals(new PathFragment("C:/a/b"), new PathFragment("C:/a/./b").normalize());
+ assertEquals(new PathFragment("C:/b"), new PathFragment("C:/a/../b").normalize());
+ assertEquals(new PathFragment("C:/../b"), new PathFragment("C:/../b").normalize());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathTest.java
new file mode 100644
index 0000000000..738e454e43
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathTest.java
@@ -0,0 +1,312 @@
+// Copyright 2014 Google Inc. 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Lists;
+import com.google.common.testing.EqualsTester;
+import com.google.common.testing.GcFinalization;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A test for {@link Path}.
+ */
+@RunWith(JUnit4.class)
+public class PathTest {
+ private FileSystem filesystem;
+ private Path root;
+
+ @Before
+ public void setUp() throws Exception {
+ filesystem = new InMemoryFileSystem(BlazeClock.instance());
+ root = filesystem.getRootDirectory();
+ Path first = root.getChild("first");
+ first.createDirectory();
+ }
+
+ @Test
+ public void testStartsWithWorksForSelf() {
+ assertStartsWithReturns(true, "/first/child", "/first/child");
+ }
+
+ @Test
+ public void testStartsWithWorksForChild() {
+ assertStartsWithReturns(true,
+ "/first/child", "/first/child/grandchild");
+ }
+
+ @Test
+ public void testStartsWithWorksForDeepDescendant() {
+ assertStartsWithReturns(true,
+ "/first/child", "/first/child/grandchild/x/y/z");
+ }
+
+ @Test
+ public void testStartsWithFailsForParent() {
+ assertStartsWithReturns(false, "/first/child", "/first");
+ }
+
+ @Test
+ public void testStartsWithFailsForSibling() {
+ assertStartsWithReturns(false, "/first/child", "/first/child2");
+ }
+
+ @Test
+ public void testStartsWithFailsForLinkToDescendant()
+ throws Exception {
+ Path linkTarget = filesystem.getPath("/first/linked_to");
+ FileSystemUtils.createEmptyFile(linkTarget);
+ Path second = filesystem.getPath("/second/");
+ second.createDirectory();
+ second.getChild("child_link").createSymbolicLink(linkTarget);
+ assertStartsWithReturns(false, "/first", "/second/child_link");
+ }
+
+ @Test
+ public void testStartsWithFailsForNullPrefix() {
+ try {
+ filesystem.getPath("/first").startsWith(null);
+ fail();
+ } catch (Exception e) {
+ }
+ }
+
+ private void assertStartsWithReturns(boolean expected,
+ String ancestor,
+ String descendant) {
+ Path parent = filesystem.getPath(ancestor);
+ Path child = filesystem.getPath(descendant);
+ assertEquals(expected, child.startsWith(parent));
+ }
+
+ @Test
+ public void testGetChildWorks() {
+ assertGetChildWorks("second");
+ assertGetChildWorks("...");
+ assertGetChildWorks("....");
+ }
+
+ private void assertGetChildWorks(String childName) {
+ assertEquals(filesystem.getPath("/first/" + childName),
+ filesystem.getPath("/first").getChild(childName));
+ }
+
+ @Test
+ public void testGetChildFailsForChildWithSlashes() {
+ assertGetChildFails("second/third");
+ assertGetChildFails("./third");
+ assertGetChildFails("../third");
+ assertGetChildFails("second/..");
+ assertGetChildFails("second/.");
+ assertGetChildFails("/third");
+ assertGetChildFails("third/");
+ }
+
+ private void assertGetChildFails(String childName) {
+ try {
+ filesystem.getPath("/first").getChild(childName);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // Expected.
+ }
+ }
+
+ @Test
+ public void testGetChildFailsForDotAndDotDot() {
+ assertGetChildFails(".");
+ assertGetChildFails("..");
+ }
+
+ @Test
+ public void testGetChildFailsForEmptyString() {
+ assertGetChildFails("");
+ }
+
+ @Test
+ public void testRelativeToWorks() {
+ assertRelativeToWorks("apple", "/fruit/apple", "/fruit");
+ assertRelativeToWorks("apple/jonagold", "/fruit/apple/jonagold", "/fruit");
+ }
+
+ @Test
+ public void testGetRelativeWithStringWorks() {
+ assertGetRelativeWorks("/first/x/y", "y");
+ assertGetRelativeWorks("/y", "/y");
+ assertGetRelativeWorks("/first/x/x", "./x");
+ assertGetRelativeWorks("/first/y", "../y");
+ assertGetRelativeWorks("/", "../../../../..");
+ }
+
+ @Test
+ public void testAsFragmentWorks() {
+ assertAsFragmentWorks("/");
+ assertAsFragmentWorks("//");
+ assertAsFragmentWorks("/first");
+ assertAsFragmentWorks("/first/x/y");
+ assertAsFragmentWorks("/first/x/y.foo");
+ }
+
+ @Test
+ public void testGetRelativeWithFragmentWorks() {
+ Path dir = filesystem.getPath("/first/x");
+ assertEquals("/first/x/y",
+ dir.getRelative(new PathFragment("y")).toString());
+ assertEquals("/first/x/x",
+ dir.getRelative(new PathFragment("./x")).toString());
+ assertEquals("/first/y",
+ dir.getRelative(new PathFragment("../y")).toString());
+
+ }
+
+ @Test
+ public void testGetRelativeWithAbsoluteFragmentWorks() {
+ Path root = filesystem.getPath("/first/x");
+ assertEquals("/x/y",
+ root.getRelative(new PathFragment("/x/y")).toString());
+ }
+
+ @Test
+ public void testGetRelativeWithAbsoluteStringWorks() {
+ Path root = filesystem.getPath("/first/x");
+ assertEquals("/x/y", root.getRelative("/x/y").toString());
+ }
+
+ @Test
+ public void testComparableSortOrder() {
+ Path zzz = filesystem.getPath("/zzz");
+ Path ZZZ = filesystem.getPath("/ZZZ");
+ Path abc = filesystem.getPath("/abc");
+ Path aBc = filesystem.getPath("/aBc");
+ Path AbC = filesystem.getPath("/AbC");
+ Path ABC = filesystem.getPath("/ABC");
+ List<Path> list = Lists.newArrayList(zzz, ZZZ, ABC, aBc, AbC, abc);
+ Collections.sort(list);
+ assertThat(list).containsExactly(ABC, AbC, ZZZ, aBc, abc, zzz).inOrder();
+ }
+
+ @Test
+ public void testParentOfRootIsRoot() {
+ assertSame(root, root.getRelative(".."));
+
+ assertSame(root.getRelative("dots"),
+ root.getRelative("broken/../../dots"));
+ }
+
+ @Test
+ public void testSingleSegmentEquivalence() {
+ assertSame(
+ root.getRelative("aSingleSegment"),
+ root.getRelative("aSingleSegment"));
+ }
+
+ @Test
+ public void testSiblingNonEquivalenceString() {
+ assertNotSame(
+ root.getRelative("aSingleSegment"),
+ root.getRelative("aDifferentSegment"));
+ }
+
+ @Test
+ public void testSiblingNonEquivalenceFragment() {
+ assertNotSame(
+ root.getRelative(new PathFragment("aSingleSegment")),
+ root.getRelative(new PathFragment("aDifferentSegment")));
+ }
+
+ @Test
+ public void testHashCodeStableAcrossGarbageCollections() {
+ Path parent = filesystem.getPath("/a");
+ PathFragment childFragment = new PathFragment("b");
+ Path child = parent.getRelative(childFragment);
+ WeakReference<Path> childRef = new WeakReference<>(child);
+ int childHashCode1 = childRef.get().hashCode();
+ assertEquals(childHashCode1, parent.getRelative(childFragment).hashCode());
+ child = null;
+ GcFinalization.awaitClear(childRef);
+ int childHashCode2 = parent.getRelative(childFragment).hashCode();
+ assertEquals(childHashCode1, childHashCode2);
+ }
+
+ @Test
+ public void testSerialization() throws Exception {
+ FileSystem oldFileSystem = Path.getFileSystemForSerialization();
+ try {
+ Path.setFileSystemForSerialization(filesystem);
+ Path root = filesystem.getPath("/");
+ Path p1 = filesystem.getPath("/foo");
+ Path p2 = filesystem.getPath("/foo/bar");
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(bos);
+
+ oos.writeObject(root);
+ oos.writeObject(p1);
+ oos.writeObject(p2);
+
+ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+ ObjectInputStream ois = new ObjectInputStream(bis);
+
+ Path dsRoot = (Path) ois.readObject();
+ Path dsP1 = (Path) ois.readObject();
+ Path dsP2 = (Path) ois.readObject();
+
+ new EqualsTester()
+ .addEqualityGroup(root, dsRoot)
+ .addEqualityGroup(p1, dsP1)
+ .addEqualityGroup(p2, dsP2)
+ .testEquals();
+
+ assertTrue(p2.startsWith(p1));
+ assertTrue(p2.startsWith(dsP1));
+ assertTrue(dsP2.startsWith(p1));
+ assertTrue(dsP2.startsWith(dsP1));
+ } finally {
+ Path.setFileSystemForSerialization(oldFileSystem);
+ }
+ }
+
+ private void assertAsFragmentWorks(String expected) {
+ assertEquals(new PathFragment(expected), filesystem.getPath(expected).asFragment());
+ }
+
+ private void assertGetRelativeWorks(String expected, String relative) {
+ assertEquals(filesystem.getPath(expected),
+ filesystem.getPath("/first/x").getRelative(relative));
+ }
+
+ private void assertRelativeToWorks(String expected, String relative, String original) {
+ assertEquals(new PathFragment(expected),
+ filesystem.getPath(relative).relativeTo(filesystem.getPath(original)));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java b/src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java
new file mode 100644
index 0000000000..c92fc2b634
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/PathWindowsTest.java
@@ -0,0 +1,98 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * A test for windows aspects of {@link Path}.
+ */
+@RunWith(JUnit4.class)
+public class PathWindowsTest {
+ private FileSystem filesystem;
+ private Path root;
+
+ @Before
+ public void setUp() throws Exception {
+ filesystem = new InMemoryFileSystem(BlazeClock.instance());
+ root = filesystem.getRootDirectory();
+ Path first = root.getChild("first");
+ first.createDirectory();
+ }
+
+ private void assertAsFragmentWorks(String expected) {
+ assertEquals(new PathFragment(expected), filesystem.getPath(expected).asFragment());
+ }
+
+ @Test
+ public void testWindowsPath() {
+ Path p = filesystem.getPath("C:/foo/bar");
+ assertEquals("C:/foo/bar", p.getPathString());
+ assertEquals("C:/foo/bar", p.toString());
+ }
+
+ @Test
+ public void testAsFragmentWindows() {
+ assertAsFragmentWorks("C:/");
+ assertAsFragmentWorks("C://");
+ assertAsFragmentWorks("C:/first");
+ assertAsFragmentWorks("C:/first/x/y");
+ assertAsFragmentWorks("C:/first/x/y.foo");
+ }
+
+ @Test
+ public void testGetRelativeWithFragmentWindows() {
+ Path dir = filesystem.getPath("C:/first/x");
+ assertEquals("C:/first/x/y",
+ dir.getRelative(new PathFragment("y")).toString());
+ assertEquals("C:/first/x/x",
+ dir.getRelative(new PathFragment("./x")).toString());
+ assertEquals("C:/first/y",
+ dir.getRelative(new PathFragment("../y")).toString());
+ assertEquals("C:/first/y",
+ dir.getRelative(new PathFragment("../y")).toString());
+ assertEquals("C:/y",
+ dir.getRelative(new PathFragment("../../../y")).toString());
+ }
+
+ @Test
+ public void testGetRelativeWithAbsoluteFragmentWindows() {
+ Path root = filesystem.getPath("C:/first/x");
+ assertEquals("C:/x/y",
+ root.getRelative(new PathFragment("C:/x/y")).toString());
+ }
+
+ @Test
+ public void testGetRelativeWithAbsoluteStringWorksWindows() {
+ Path root = filesystem.getPath("C:/first/x");
+ assertEquals("C:/x/y", root.getRelative("C:/x/y").toString());
+ }
+
+ @Test
+ public void testParentOfRootIsRootWindows() {
+ assertSame(root, root.getRelative(".."));
+
+ assertSame(root.getRelative("dots"),
+ root.getRelative("broken/../../dots"));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/RecursiveGlobTest.java b/src/test/java/com/google/devtools/build/lib/vfs/RecursiveGlobTest.java
new file mode 100644
index 0000000000..5e0012ac08
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/RecursiveGlobTest.java
@@ -0,0 +1,227 @@
+// Copyright 2014 Google Inc. 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 static com.google.devtools.build.lib.testutil.MoreAsserts.assertSameContents;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests {@link UnixGlob} recursive globs.
+ */
+@RunWith(JUnit4.class)
+public class RecursiveGlobTest {
+
+ private Path tmpPath;
+ private FileSystem fileSystem;
+
+ @Before
+ public void setUp() throws Exception {
+ fileSystem = new InMemoryFileSystem(BlazeClock.instance());
+ tmpPath = fileSystem.getPath("/rglobtmp");
+ for (String dir : ImmutableList.of("foo/bar/wiz",
+ "foo/baz/wiz",
+ "foo/baz/quip/wiz",
+ "food/baz/wiz",
+ "fool/baz/wiz")) {
+ FileSystemUtils.createDirectoryAndParents(tmpPath.getRelative(dir));
+ }
+ FileSystemUtils.createEmptyFile(tmpPath.getRelative("foo/bar/wiz/file"));
+ }
+
+ @Test
+ public void testDoubleStar() throws Exception {
+ assertGlobMatches("**", ".", "foo", "foo/bar", "foo/bar/wiz", "foo/baz", "foo/baz/quip",
+ "foo/baz/quip/wiz", "foo/baz/wiz", "foo/bar/wiz/file", "food", "food/baz",
+ "food/baz/wiz", "fool", "fool/baz", "fool/baz/wiz");
+ }
+
+ @Test
+ public void testDoubleDoubleStar() throws Exception {
+ assertGlobMatches("**/**", ".", "foo", "foo/bar", "foo/bar/wiz", "foo/baz", "foo/baz/quip",
+ "foo/baz/quip/wiz", "foo/baz/wiz", "foo/bar/wiz/file", "food", "food/baz",
+ "food/baz/wiz", "fool", "fool/baz", "fool/baz/wiz");
+ }
+
+ @Test
+ public void testDirectoryWithDoubleStar() throws Exception {
+ assertGlobMatches("foo/**", "foo", "foo/bar", "foo/bar/wiz", "foo/baz", "foo/baz/quip",
+ "foo/baz/quip/wiz", "foo/baz/wiz", "foo/bar/wiz/file");
+ }
+
+ @Test
+ public void testIllegalPatterns() 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")) {
+ assertIllegalWildcard(prefix + pattern);
+ assertIllegalWildcard(pattern + suffix);
+ assertIllegalWildcard("foo", pattern + suffix);
+ }
+ }
+ }
+
+ @Test
+ public void testDoubleStarPatternWithNamedChild() throws Exception {
+ assertGlobMatches("**/bar", "foo/bar");
+ }
+
+ @Test
+ public void testDoubleStarPatternWithChildGlob() throws Exception {
+ assertGlobMatches("**/ba*",
+ "foo/bar", "foo/baz", "food/baz", "fool/baz");
+ }
+
+ @Test
+ public void testDoubleStarAsChildGlob() throws Exception {
+ assertGlobMatches("foo/**/wiz", "foo/bar/wiz", "foo/baz/quip/wiz", "foo/baz/wiz");
+ }
+
+ @Test
+ public void testDoubleStarUnderNonexistentDirectory() throws Exception {
+ assertGlobMatches("not-there/**" /* => nothing */);
+ }
+
+ @Test
+ public void testDoubleStarGlobWithNonExistentBase() throws Exception {
+ Collection<Path> globResult = UnixGlob.forPath(fileSystem.getPath("/does/not/exist"))
+ .addPattern("**")
+ .globInterruptible();
+ assertEquals(0, globResult.size());
+ }
+
+ @Test
+ public void testDoubleStarUnderFile() throws Exception {
+ assertGlobMatches("foo/bar/wiz/file/**" /* => nothing */);
+ }
+
+ @Test
+ public void testSingleFileExclude() throws Exception {
+ assertGlobWithExcludeMatches("**", "food", ".", "foo", "foo/bar", "foo/bar/wiz", "foo/baz",
+ "foo/baz/quip", "foo/baz/quip/wiz", "foo/baz/wiz",
+ "foo/bar/wiz/file", "food/baz", "food/baz/wiz", "fool", "fool/baz",
+ "fool/baz/wiz");
+ }
+
+ @Test
+ public void testSingleFileExcludeForDirectoryWithChildGlob()
+ throws Exception {
+ assertGlobWithExcludeMatches("foo/**", "foo", "foo/bar", "foo/bar/wiz", "foo/baz",
+ "foo/baz/quip", "foo/baz/quip/wiz", "foo/baz/wiz",
+ "foo/bar/wiz/file");
+ }
+
+ @Test
+ public void testGlobExcludeForDirectoryWithChildGlob()
+ throws Exception {
+ assertGlobWithExcludeMatches("foo/**", "foo/*", "foo", "foo/bar/wiz", "foo/baz/quip",
+ "foo/baz/quip/wiz", "foo/baz/wiz", "foo/bar/wiz/file");
+ }
+
+ @Test
+ public void testExcludeAll() throws Exception {
+ assertGlobWithExcludesMatches(Lists.newArrayList("**"),
+ Lists.newArrayList("*", "*/*", "*/*/*", "*/*/*/*"), ".");
+ }
+
+ @Test
+ public void testManualGlobExcludeForDirectoryWithChildGlob()
+ throws Exception {
+ assertGlobWithExcludesMatches(Lists.newArrayList("foo/**"),
+ Lists.newArrayList("foo", "foo/*", "foo/*/*", "foo/*/*/*"));
+ }
+
+ private void assertGlobMatches(String pattern, String... expecteds)
+ throws Exception {
+ assertGlobWithExcludesMatches(
+ Collections.singleton(pattern), Collections.<String>emptyList(),
+ expecteds);
+ }
+
+ private void assertGlobWithExcludeMatches(String pattern, String exclude,
+ String... expecteds)
+ throws Exception {
+ assertGlobWithExcludesMatches(
+ Collections.singleton(pattern), Collections.singleton(exclude),
+ expecteds);
+ }
+
+ private void assertGlobWithExcludesMatches(Collection<String> pattern,
+ Collection<String> excludes,
+ String... expecteds) throws Exception {
+ assertSameContents(resolvePaths(expecteds),
+ new UnixGlob.Builder(tmpPath)
+ .addPatterns(pattern)
+ .addExcludes(excludes)
+ .globInterruptible());
+ }
+
+ private Set<Path> resolvePaths(String... relativePaths) {
+ Set<Path> expectedFiles = new HashSet<>();
+ for (String expected : relativePaths) {
+ Path file = expected.equals(".")
+ ? tmpPath
+ : tmpPath.getRelative(expected);
+ expectedFiles.add(file);
+ }
+ return expectedFiles;
+ }
+
+ /**
+ * Tests that a recursive glob returns files in sorted order.
+ */
+ @Test
+ public void testGlobEntriesAreSorted() throws Exception {
+ List<Path> globResult = new UnixGlob.Builder(tmpPath)
+ .addPattern("**")
+ .setExcludeDirectories(false)
+ .globInterruptible();
+
+ assertThat(Ordering.natural().sortedCopy(globResult)).containsExactlyElementsIn(globResult)
+ .inOrder();
+ }
+
+ private void assertIllegalWildcard(String pattern, String... excludePatterns)
+ throws Exception {
+ try {
+ new UnixGlob.Builder(tmpPath)
+ .addPattern(pattern)
+ .addExcludes(excludePatterns)
+ .globInterruptible();
+ fail();
+ } catch (IllegalArgumentException e) {
+ MoreAsserts.assertContainsRegex("recursive wildcard must be its own segment", e.getMessage());
+ }
+ }
+
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/RootedPathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/RootedPathTest.java
new file mode 100644
index 0000000000..46d286dcdc
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/RootedPathTest.java
@@ -0,0 +1,56 @@
+// Copyright 2014 Google Inc. 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 com.google.common.testing.EqualsTester;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link RootedPath}.
+ */
+@RunWith(JUnit4.class)
+public class RootedPathTest {
+ private FileSystem filesystem;
+ private Path root;
+
+ @Before
+ public void setUp() throws Exception {
+ filesystem = new InMemoryFileSystem(BlazeClock.instance());
+ root = filesystem.getRootDirectory();
+ }
+
+ @Test
+ public void testEqualsAndHashCodeContract() throws Exception {
+ Path pkgRoot1 = root.getRelative("pkgroot1");
+ Path pkgRoot2 = root.getRelative("pkgroot2");
+ RootedPath rootedPathA1 = RootedPath.toRootedPath(pkgRoot1, new PathFragment("foo/bar"));
+ RootedPath rootedPathA2 = RootedPath.toRootedPath(pkgRoot1, new PathFragment("foo/bar"));
+ RootedPath absolutePath1 = RootedPath.toRootedPath(root, new PathFragment("pkgroot1/foo/bar"));
+ RootedPath rootedPathB1 = RootedPath.toRootedPath(pkgRoot2, new PathFragment("foo/bar"));
+ RootedPath rootedPathB2 = RootedPath.toRootedPath(pkgRoot2, new PathFragment("foo/bar"));
+ RootedPath absolutePath2 = RootedPath.toRootedPath(root, new PathFragment("pkgroot2/foo/bar"));
+ new EqualsTester()
+ .addEqualityGroup(rootedPathA1, rootedPathA2)
+ .addEqualityGroup(rootedPathB1, rootedPathB2)
+ .addEqualityGroup(absolutePath1)
+ .addEqualityGroup(absolutePath2)
+ .testEquals();
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
new file mode 100644
index 0000000000..6c8071f075
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java
@@ -0,0 +1,806 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+
+/**
+ * Generic tests for any file system that implements {@link ScopeEscapableFileSystem},
+ * i.e. any file system that supports symlinks that escape its scope.
+ *
+ * Each suitable file system test should inherit from this class, thereby obtaining
+ * all the tests.
+ */
+public abstract class ScopeEscapableFileSystemTest extends SymlinkAwareFileSystemTest {
+
+ /**
+ * Trivial FileSystem implementation that can record the last path passed to each method
+ * and read/write to a unified "state" variable (which can then be checked by tests) for
+ * each data type this class manipulates.
+ *
+ * The default implementation of each method throws an exception. Each test case should
+ * selectively override the methods it expects to be invoked.
+ */
+ private static class TestDelegator extends FileSystem {
+ protected Path lastPath;
+ protected boolean booleanState;
+ protected long longState;
+ protected Object objectState;
+
+ public void setState(boolean state) { booleanState = state; }
+ public void setState(long state) { longState = state; }
+ public void setState(Object state) { objectState = state; }
+
+ public boolean booleanState() { return booleanState; }
+ public long longState() { return longState; }
+ public Object objectState() { return objectState; }
+
+ public PathFragment lastPath() {
+ Path ans = lastPath;
+ // Clear this out to protect against accidental matches when testing the same path multiple
+ // consecutive times.
+ lastPath = null;
+ return ans != null ? ans.asFragment() : null;
+ }
+
+ @Override public boolean supportsModifications() { return true; }
+ @Override public boolean supportsSymbolicLinks() { return true; }
+
+ private static RuntimeException re() {
+ return new RuntimeException("This method should not be called in this context");
+ }
+
+ @Override protected boolean isReadable(Path path) { throw re(); }
+ @Override protected boolean isWritable(Path path) { throw re(); }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { throw re(); }
+ @Override protected boolean isFile(Path path, boolean followSymlinks) { throw re(); }
+ @Override protected boolean isExecutable(Path path) { throw re(); }
+ @Override protected boolean exists(Path path, boolean followSymlinks) {throw re(); }
+ @Override protected boolean isSymbolicLink(Path path) { throw re(); }
+ @Override protected boolean createDirectory(Path path) { throw re(); }
+ @Override protected boolean delete(Path path) { throw re(); }
+
+ @Override protected long getFileSize(Path path, boolean followSymlinks) { throw re(); }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { throw re(); }
+
+ @Override protected void setWritable(Path path, boolean writable) { throw re(); }
+ @Override protected void setExecutable(Path path, boolean executable) { throw re(); }
+ @Override protected void setReadable(Path path, boolean readable) { throw re(); }
+ @Override protected void setLastModifiedTime(Path path, long newTime) { throw re(); }
+ @Override protected void renameTo(Path sourcePath, Path targetPath) { throw re(); }
+ @Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) {
+ throw re();
+ }
+
+ @Override protected PathFragment readSymbolicLink(Path path) { throw re(); }
+ @Override protected InputStream getInputStream(Path path) { throw re(); }
+ @Override protected Collection<Path> getDirectoryEntries(Path path) { throw re(); }
+ @Override protected OutputStream getOutputStream(Path path, boolean append) { throw re(); }
+ @Override
+ protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException {
+ throw re();
+ }
+ }
+
+ protected static final PathFragment SCOPE_ROOT = new PathFragment("/fs/root");
+
+ private Path fileLink;
+ private PathFragment fileLinkTarget;
+ private Path dirLink;
+ private PathFragment dirLinkTarget;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ Preconditions.checkState(testFS instanceof ScopeEscapableFileSystem,
+ "Not ScopeEscapable: " + testFS);
+ ((ScopeEscapableFileSystem) testFS).enableScopeChecking(false);
+ for (int i = 1; i <= SCOPE_ROOT.segmentCount(); i++) {
+ testFS.getPath(SCOPE_ROOT.subFragment(0, i)).createDirectory();
+ }
+
+ fileLink = testFS.getPath(SCOPE_ROOT.getRelative("link"));
+ fileLinkTarget = new PathFragment("/should/be/delegated/fileLinkTarget");
+ testFS.createSymbolicLink(fileLink, fileLinkTarget);
+
+ dirLink = testFS.getPath(SCOPE_ROOT.getRelative("dirlink"));
+ dirLinkTarget = new PathFragment("/should/be/delegated/dirLinkTarget");
+ testFS.createSymbolicLink(dirLink, dirLinkTarget);
+ }
+
+ /**
+ * Returns the file system supplied by {@link #getFreshFileSystem}, cast to
+ * a {@link ScopeEscapableFileSystem}. Also enables scope checking within
+ * the file system (which we keep disabled for inherited tests that aren't
+ * intended to test scope boundaries).
+ */
+ private ScopeEscapableFileSystem scopedFS() {
+ ScopeEscapableFileSystem fs = (ScopeEscapableFileSystem) testFS;
+ fs.enableScopeChecking(true);
+ return fs;
+ }
+
+ // Checks that the semi-resolved path passed to the delegator matches the expected value.
+ private void checkPath(TestDelegator delegator, PathFragment expectedDelegatedPath) {
+ assertTrue(expectedDelegatedPath.equals(delegator.lastPath()));
+ }
+
+ // Asserts that the condition is false and checks that the expected path was delegated.
+ private void assertFalseWithPathCheck(boolean result, TestDelegator delegator,
+ PathFragment expectedDelegatedPath) {
+ assertFalse(result);
+ checkPath(delegator, expectedDelegatedPath);
+ }
+
+ // Asserts that the condition is true and checks that the expected path was delegated.
+ private void assertTrueWithPathCheck(boolean result, TestDelegator delegator,
+ PathFragment expectedDelegatedPath) {
+ assertTrue(result);
+ checkPath(delegator, expectedDelegatedPath);
+ }
+
+ /////////////////////////////////////////////////////////////////////////////
+ // Tests:
+ /////////////////////////////////////////////////////////////////////////////
+
+ @Test
+ public void testIsReadableCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isReadable(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isReadable(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isReadable(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isReadable(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isReadable(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsWritableCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isWritable(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isWritable(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isWritable(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isWritable(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isWritable(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testisExecutableCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isExecutable(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isExecutable(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isExecutable(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isExecutable(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isExecutable(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsDirectoryCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean exists(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isDirectory(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isDirectory(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsFileCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isFile(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean exists(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.isFile(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").isFile(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.isFile(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isFile(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testIsSymbolicLinkCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean isSymbolicLink(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean exists(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ // We shouldn't follow final-segment links, so they should never invoke the delegator.
+ delegator.setState(false);
+ assertTrue(fileLink.isSymbolicLink());
+ assertTrue(delegator.lastPath() == null);
+
+ assertFalseWithPathCheck(dirLink.getRelative("a").isSymbolicLink(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(dirLink.getRelative("a").isSymbolicLink(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ /**
+ * Returns a test delegator that reflects info passed to Path.exists() calls.
+ */
+ private TestDelegator newExistsDelegator() {
+ return new TestDelegator() {
+ @Override protected boolean exists(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected FileStatus stat(Path path, boolean followSymlinks) throws IOException {
+ if (!exists(path, followSymlinks)) {
+ throw new IOException("Expected exception on stat of non-existent file");
+ }
+ return super.stat(path, followSymlinks);
+ }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ }
+
+ @Test
+ public void testExistsCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = newExistsDelegator();
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(fileLink.exists(), delegator, fileLinkTarget);
+ assertFalseWithPathCheck(dirLink.getRelative("a").exists(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(fileLink.exists(), delegator, fileLinkTarget);
+ assertTrueWithPathCheck(dirLink.getRelative("a").exists(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCreateDirectoryCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean createDirectory(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertFalseWithPathCheck(dirLink.getRelative("a").createDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(dirLink.getRelative("a").createDirectory(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testDeleteCallOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected boolean delete(Path path) {
+ lastPath = path;
+ return booleanState();
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ assertTrue(fileLink.delete());
+ assertTrue(delegator.lastPath() == null); // Deleting a link shouldn't require delegation.
+ assertFalseWithPathCheck(dirLink.getRelative("a").delete(), delegator,
+ dirLinkTarget.getRelative("a"));
+
+ delegator.setState(true);
+ assertTrueWithPathCheck(dirLink.getRelative("a").delete(), delegator,
+ dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallGetFileSizeOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected long getFileSize(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return longState();
+ }
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ final int state1 = 10;
+ delegator.setState(state1);
+ assertEquals(state1, fileLink.getFileSize());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state1, dirLink.getRelative("a").getFileSize());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+
+ final int state2 = 10;
+ delegator.setState(state2);
+ assertEquals(state2, fileLink.getFileSize());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state2, dirLink.getRelative("a").getFileSize());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallGetLastModifiedTimeOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) {
+ lastPath = path;
+ return longState();
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ final int state1 = 10;
+ delegator.setState(state1);
+ assertEquals(state1, fileLink.getLastModifiedTime());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state1, dirLink.getRelative("a").getLastModifiedTime());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+
+ final int state2 = 10;
+ delegator.setState(state2);
+ assertEquals(state2, fileLink.getLastModifiedTime());
+ checkPath(delegator, fileLinkTarget);
+ assertEquals(state2, dirLink.getRelative("a").getLastModifiedTime());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetReadableOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setReadable(Path path, boolean readable) {
+ lastPath = path;
+ setState(readable);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ fileLink.setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+
+ delegator.setState(false);
+ dirLink.getRelative("a").setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetWritableOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setWritable(Path path, boolean writable) {
+ lastPath = path;
+ setState(writable);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ fileLink.setWritable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setWritable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+
+ delegator.setState(false);
+ dirLink.getRelative("a").setWritable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setWritable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetExecutableOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setReadable(Path path, boolean readable) {
+ lastPath = path;
+ setState(readable);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(false);
+ fileLink.setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, fileLinkTarget);
+
+ delegator.setState(false);
+ dirLink.getRelative("a").setReadable(true);
+ assertTrue(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setReadable(false);
+ assertFalse(delegator.booleanState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallSetLastModifiedTimeOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void setLastModifiedTime(Path path, long newTime) {
+ lastPath = path;
+ setState(newTime);
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(0);
+ fileLink.setLastModifiedTime(10);
+ assertEquals(10, delegator.longState());
+ checkPath(delegator, fileLinkTarget);
+ fileLink.setLastModifiedTime(15);
+ assertEquals(15, delegator.longState());
+ checkPath(delegator, fileLinkTarget);
+
+ dirLink.getRelative("a").setLastModifiedTime(20);
+ assertEquals(20, delegator.longState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ dirLink.getRelative("a").setLastModifiedTime(25);
+ assertEquals(25, delegator.longState());
+ checkPath(delegator, dirLinkTarget.getRelative("a"));
+ }
+
+ @Test
+ public void testCallRenameToOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void renameTo(Path sourcePath, Path targetPath) {
+ lastPath = sourcePath;
+ setState(targetPath);
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ // Renaming a link should work fine.
+ delegator.setState(null);
+ fileLink.renameTo(testFS.getPath(SCOPE_ROOT).getRelative("newname"));
+ assertEquals(null, delegator.lastPath()); // Renaming a link shouldn't require delegation.
+ assertEquals(null, delegator.objectState());
+
+ // Renaming an out-of-scope path to an in-scope path should fail due to filesystem mismatch
+ // errors.
+ Path newPath = testFS.getPath(SCOPE_ROOT.getRelative("blah"));
+ try {
+ dirLink.getRelative("a").renameTo(newPath);
+ fail("This is an attempt at a cross-filesystem renaming, which should fail");
+ } catch (IOException e) {
+ // Expected.
+ }
+
+ // Renaming an out-of-scope path to another out-of-scope path can be valid.
+ newPath = dirLink.getRelative("b");
+ dirLink.getRelative("a").renameTo(newPath);
+ assertEquals(dirLinkTarget.getRelative("a"), delegator.lastPath());
+ assertEquals(dirLinkTarget.getRelative("b"), ((Path) delegator.objectState()).asFragment());
+ }
+
+ @Test
+ public void testCallCreateSymbolicLinkOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) {
+ lastPath = linkPath;
+ setState(targetFragment);
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ PathFragment newLinkTarget = new PathFragment("/something/else");
+ dirLink.getRelative("a").createSymbolicLink(newLinkTarget);
+ assertEquals(dirLinkTarget.getRelative("a"), delegator.lastPath());
+ assertSame(newLinkTarget, delegator.objectState());
+ }
+
+ @Test
+ public void testCallReadSymbolicLinkOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected PathFragment readSymbolicLink(Path path) {
+ lastPath = path;
+ return (PathFragment) objectState;
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ // Since we're not following the link, this shouldn't invoke delegation.
+ delegator.setState(new PathFragment("whatever"));
+ PathFragment p = fileLink.readSymbolicLink();
+ assertEquals(null, delegator.lastPath());
+ assertNotSame(delegator.objectState(), p);
+
+ // This should.
+ p = dirLink.getRelative("a").readSymbolicLink();
+ assertEquals(dirLinkTarget.getRelative("a"), delegator.lastPath());
+ assertSame(delegator.objectState(), p);
+ }
+
+ @Test
+ public void testCallGetInputStreamOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected InputStream getInputStream(Path path) {
+ lastPath = path;
+ return (InputStream) objectState;
+ }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(new ByteArrayInputStream("blah".getBytes()));
+ InputStream is = fileLink.getInputStream();
+ assertEquals(fileLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), is);
+
+ delegator.setState(new ByteArrayInputStream("blah2".getBytes()));
+ is = dirLink.getInputStream();
+ assertEquals(dirLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), is);
+ }
+
+ @Test
+ public void testCallGetOutputStreamOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected OutputStream getOutputStream(Path path, boolean append) {
+ lastPath = path;
+ return (OutputStream) objectState;
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(new ByteArrayOutputStream());
+ OutputStream os = fileLink.getOutputStream();
+ assertEquals(fileLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), os);
+
+ delegator.setState(new ByteArrayOutputStream());
+ os = dirLink.getOutputStream();
+ assertEquals(dirLinkTarget, delegator.lastPath());
+ assertSame(delegator.objectState(), os);
+ }
+
+ @Test
+ public void testCallGetDirectoryEntriesOnEscapingSymlink() throws Exception {
+ TestDelegator delegator = new TestDelegator() {
+ @Override protected Collection<Path> getDirectoryEntries(Path path) {
+ lastPath = path;
+ return ImmutableList.of((Path) objectState);
+ }
+ @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; }
+ };
+ scopedFS().setDelegator(delegator);
+
+ delegator.setState(testFS.getPath("/anything"));
+ Collection<Path> entries = dirLink.getDirectoryEntries();
+ assertEquals(dirLinkTarget, delegator.lastPath());
+ assertEquals(1, entries.size());
+ assertSame(delegator.objectState(), entries.iterator().next());
+ }
+
+ /**
+ * Asserts that "link" is an in-scope link that doesn't result in an out-of-FS
+ * delegation. If link is relative, its path is relative to SCOPE_ROOT.
+ *
+ * Note that we don't actually check that the canonicalized target path matches
+ * the link's target value. Such testing should be covered by
+ * SymlinkAwareFileSystemTest.
+ */
+ private void assertInScopeLink(String link, String target, TestDelegator d) throws IOException {
+ Path l = testFS.getPath(SCOPE_ROOT.getRelative(link));
+ testFS.createSymbolicLink(l, new PathFragment(target));
+ l.exists();
+ assertNull(d.lastPath());
+ }
+
+ /**
+ * Asserts that "link" is an out-of-scope link and that the re-delegated path
+ * matches expectedPath. If link is relative, its path is relative to SCOPE_ROOT.
+ */
+ private void assertOutOfScopeLink(String link, String target, String expectedPath,
+ TestDelegator d) throws IOException {
+ Path l = testFS.getPath(SCOPE_ROOT.getRelative(link));
+ testFS.createSymbolicLink(l, new PathFragment(target));
+ l.exists();
+ assertEquals(expectedPath, d.lastPath().getPathString());
+ }
+
+ /**
+ * Returns the scope root with the final n segments chopped off (or a 0-segment path
+ * if n > SCOPE_ROOT.segmentCount()).
+ */
+ private String chopScopeRoot(int n) {
+ return SCOPE_ROOT
+ .subFragment(0, n > SCOPE_ROOT.segmentCount() ? 0 : SCOPE_ROOT.segmentCount() - n)
+ .getPathString();
+ }
+
+ /**
+ * Tests that absolute symlinks with ".." and "." segments are delegated to
+ * the expected paths.
+ */
+ @Test
+ public void testAbsoluteSymlinksWithParentReferences() throws Exception {
+ TestDelegator d = newExistsDelegator();
+ scopedFS().setDelegator(d);
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir")));
+ String scopeRoot = SCOPE_ROOT.getPathString();
+ String scopeBase = SCOPE_ROOT.getBaseName();
+
+ // Symlinks that should never escape our scope.
+ assertInScopeLink("ilink1", scopeRoot, d);
+ assertInScopeLink("ilink2", scopeRoot + "/target", d);
+ assertInScopeLink("ilink3", scopeRoot + "/dir/../target", d);
+ assertInScopeLink("ilink4", scopeRoot + "/dir/../dir/dir2/../target", d);
+ assertInScopeLink("ilink5", scopeRoot + "/./dir/.././target", d);
+ assertInScopeLink("ilink6", scopeRoot + "/../" + scopeBase + "/target", d);
+ assertInScopeLink("ilink7", "/some/path/../.." + scopeRoot + "/target", d);
+
+ // Symlinks that should escape our scope.
+ assertOutOfScopeLink("olink1", scopeRoot + "/../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink2", "/some/other/path", "/some/other/path", d);
+ assertOutOfScopeLink("olink3", scopeRoot + "/../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink4", chopScopeRoot(1) + "/target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink5", scopeRoot + "/../../../../target", "/target", d);
+
+ // In-scope symlink that's not the final segment in a query.
+ Path iDirLink = testFS.getPath(SCOPE_ROOT.getRelative("ilinkdir"));
+ testFS.createSymbolicLink(iDirLink, SCOPE_ROOT.getRelative("dir"));
+ iDirLink.getRelative("file").exists();
+ assertNull(d.lastPath());
+
+ // Out-of-scope symlink that's not the final segment in a query.
+ Path oDirLink = testFS.getPath(SCOPE_ROOT.getRelative("olinkdir"));
+ testFS.createSymbolicLink(oDirLink, new PathFragment("/some/other/dir"));
+ oDirLink.getRelative("file").exists();
+ assertEquals("/some/other/dir/file", d.lastPath().getPathString());
+ }
+
+ /**
+ * Tests that relative symlinks with ".." and "." segments are delegated to
+ * the expected paths.
+ */
+ @Test
+ public void testRelativeSymlinksWithParentReferences() throws Exception {
+ TestDelegator d = newExistsDelegator();
+ scopedFS().setDelegator(d);
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir")));
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2")));
+ testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/dir3")));
+ String scopeRoot = SCOPE_ROOT.getPathString();
+ String scopeBase = SCOPE_ROOT.getBaseName();
+
+ // Symlinks that should never escape our scope.
+ assertInScopeLink("ilink1", "target", d);
+ assertInScopeLink("ilink2", "dir/../otherdir/target", d);
+ assertInScopeLink("dir/ilink3", "../target", d);
+ assertInScopeLink("dir/dir2/ilink4", "../../target", d);
+ assertInScopeLink("dir/dir2/ilink5", ".././../dir/./target", d);
+ assertInScopeLink("dir/dir2/ilink6", "../dir2/../../dir/dir2/dir3/../../../target", d);
+
+ // Symlinks that should escape our scope.
+ assertOutOfScopeLink("olink1", "../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("dir/olink2", "../../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("olink3", "../" + scopeBase + "/target", scopeRoot + "/target", d);
+ assertOutOfScopeLink("dir/dir2/olink5", "../../../target", chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("dir/dir2/olink6", "../dir2/../../dir/dir2/../../../target",
+ chopScopeRoot(1) + "/target", d);
+ assertOutOfScopeLink("dir/olink7", "../../../target", chopScopeRoot(2) + "target", d);
+ assertOutOfScopeLink("olink8", "../../../../../target", "/target", d);
+
+ // In-scope symlink that's not the final segment in a query.
+ Path iDirLink = testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/ilinkdir"));
+ testFS.createSymbolicLink(iDirLink, new PathFragment("../../dir"));
+ iDirLink.getRelative("file").exists();
+ assertNull(d.lastPath());
+
+ // Out-of-scope symlink that's not the final segment in a query.
+ Path oDirLink = testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/olinkdir"));
+ testFS.createSymbolicLink(oDirLink, new PathFragment("../../../other/dir"));
+ oDirLink.getRelative("file").exists();
+ assertEquals(chopScopeRoot(1) + "/other/dir/file", d.lastPath().getPathString());
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java
new file mode 100644
index 0000000000..a728c88796
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/SymlinkAwareFileSystemTest.java
@@ -0,0 +1,717 @@
+// Copyright 2014 Google Inc. 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.devtools.build.lib.testutil.MoreAsserts;
+import com.google.devtools.build.lib.vfs.FileSystem.NotASymlinkException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * This class handles the generic tests that any filesystem must pass.
+ *
+ * <p>Each filesystem-test should inherit from this class, thereby obtaining
+ * all the tests.
+ */
+public abstract class SymlinkAwareFileSystemTest extends FileSystemTest {
+
+ protected Path xLinkToFile;
+ protected Path xLinkToLinkToFile;
+ protected Path xLinkToDirectory;
+ protected Path xDanglingLink;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ // % ls -lR
+ // -rw-rw-r-- xFile
+ // drwxrwxr-x xNonEmptyDirectory
+ // -rw-rw-r-- xNonEmptyDirectory/foo
+ // drwxrwxr-x xEmptyDirectory
+ // lrwxrwxr-x xLinkToFile -> xFile
+ // lrwxrwxr-x xLinkToDirectory -> xEmptyDirectory
+ // lrwxrwxr-x xLinkToLinkToFile -> xLinkToFile
+ // lrwxrwxr-x xDanglingLink -> xNothing
+
+ xLinkToFile = absolutize("xLinkToFile");
+ xLinkToLinkToFile = absolutize("xLinkToLinkToFile");
+ xLinkToDirectory = absolutize("xLinkToDirectory");
+ xDanglingLink = absolutize("xDanglingLink");
+
+ createSymbolicLink(xLinkToFile, xFile);
+ createSymbolicLink(xLinkToLinkToFile, xLinkToFile);
+ createSymbolicLink(xLinkToDirectory, xEmptyDirectory);
+ createSymbolicLink(xDanglingLink, xNothing);
+ }
+
+ @Test
+ public void testCreateLinkToFile() throws IOException {
+ Path newPath = xEmptyDirectory.getChild("new-file");
+ FileSystemUtils.createEmptyFile(newPath);
+
+ Path linkPath = xEmptyDirectory.getChild("some-link");
+
+ createSymbolicLink(linkPath, newPath);
+
+ assertTrue(linkPath.isSymbolicLink());
+
+ assertTrue(linkPath.isFile());
+ assertFalse(linkPath.isFile(Symlinks.NOFOLLOW));
+ assertTrue(linkPath.isFile(Symlinks.FOLLOW));
+
+ assertFalse(linkPath.isDirectory());
+ assertFalse(linkPath.isDirectory(Symlinks.NOFOLLOW));
+ assertFalse(linkPath.isDirectory(Symlinks.FOLLOW));
+
+ if (supportsSymlinks) {
+ assertEquals(newPath.toString().length(), linkPath.getFileSize(Symlinks.NOFOLLOW));
+ assertEquals(newPath.getFileSize(Symlinks.NOFOLLOW), linkPath.getFileSize());
+ }
+ assertEquals(2,
+ linkPath.getParentDirectory().getDirectoryEntries().size());
+ assertThat(linkPath.getParentDirectory().getDirectoryEntries()).containsExactly(newPath,
+ linkPath);
+ }
+
+ @Test
+ public void testCreateLinkToDirectory() throws IOException {
+ Path newPath = xEmptyDirectory.getChild("new-file");
+ newPath.createDirectory();
+
+ Path linkPath = xEmptyDirectory.getChild("some-link");
+
+ createSymbolicLink(linkPath, newPath);
+
+ assertTrue(linkPath.isSymbolicLink());
+ assertFalse(linkPath.isFile());
+ assertTrue(linkPath.isDirectory());
+ assertEquals(2,
+ linkPath.getParentDirectory().getDirectoryEntries().size());
+ assertThat(linkPath.getParentDirectory().
+ getDirectoryEntries()).containsExactly(newPath, linkPath);
+ }
+
+ @Test
+ public void testFileCanonicalPath() throws IOException {
+ Path newPath = absolutize("new-file");
+ FileSystemUtils.createEmptyFile(newPath);
+ newPath = newPath.resolveSymbolicLinks();
+
+ Path link1 = absolutize("some-link");
+ Path link2 = absolutize("some-link2");
+
+ createSymbolicLink(link1, newPath);
+ createSymbolicLink(link2, link1);
+
+ assertCanonicalPathsMatch(newPath, link1, link2);
+ }
+
+ @Test
+ public void testDirectoryCanonicalPath() throws IOException {
+ Path newPath = absolutize("new-folder");
+ newPath.createDirectory();
+ newPath = newPath.resolveSymbolicLinks();
+
+ Path newFile = newPath.getChild("file");
+ FileSystemUtils.createEmptyFile(newFile);
+
+ Path link1 = absolutize("some-link");
+ Path link2 = absolutize("some-link2");
+
+ createSymbolicLink(link1, newPath);
+ createSymbolicLink(link2, link1);
+
+ Path linkFile1 = link1.getChild("file");
+ Path linkFile2 = link2.getChild("file");
+
+ assertCanonicalPathsMatch(newFile, linkFile1, linkFile2);
+ }
+
+ private void assertCanonicalPathsMatch(Path newPath, Path link1, Path link2)
+ throws IOException {
+ assertEquals(newPath, link1.resolveSymbolicLinks());
+ assertEquals(newPath, link2.resolveSymbolicLinks());
+ }
+
+ //
+ // createDirectory
+ //
+
+ @Test
+ public void testCreateDirectoryWhereDanglingSymlinkAlreadyExists() {
+ try {
+ xDanglingLink.createDirectory();
+ fail();
+ } catch (IOException e) {
+ assertEquals(xDanglingLink + " (File exists)", e.getMessage());
+ }
+ assertTrue(xDanglingLink.isSymbolicLink()); // still a symbolic link
+ assertFalse(xDanglingLink.isDirectory(Symlinks.FOLLOW)); // link still dangles
+ }
+
+ @Test
+ public void testCreateDirectoryWhereSymlinkAlreadyExists() {
+ try {
+ xLinkToDirectory.createDirectory();
+ fail();
+ } catch (IOException e) {
+ assertEquals(xLinkToDirectory + " (File exists)", e.getMessage());
+ }
+ assertTrue(xLinkToDirectory.isSymbolicLink()); // still a symbolic link
+ assertTrue(xLinkToDirectory.isDirectory(Symlinks.FOLLOW)); // link still points to dir
+ }
+
+ // createSymbolicLink(PathFragment)
+
+ @Test
+ public void testCreateSymbolicLinkFromFragment() throws IOException {
+ String[] linkTargets = {
+ "foo",
+ "foo/bar",
+ ".",
+ "..",
+ "../foo",
+ "../../foo",
+ "../../../../../../../../../../../../../../../../../../../../../foo",
+ "/foo",
+ "/foo/bar",
+ "/..",
+ "/foo/../bar",
+ };
+ Path linkPath = absolutize("link");
+ for (String linkTarget : linkTargets) {
+ PathFragment relative = new PathFragment(linkTarget);
+ linkPath.delete();
+ createSymbolicLink(linkPath, relative);
+ if (supportsSymlinks) {
+ assertEquals(linkTarget.length(), linkPath.getFileSize(Symlinks.NOFOLLOW));
+ assertEquals(relative, linkPath.readSymbolicLink());
+ }
+ }
+ }
+
+ @Test
+ public void testLinkToRootResolvesCorrectly() throws IOException {
+ Path rootPath = testFS.getPath("/");
+ Path linkPath = absolutize("link");
+ createSymbolicLink(linkPath, rootPath);
+
+ // resolveSymbolicLinks requires an existing path:
+ try {
+ linkPath.getRelative("test").resolveSymbolicLinks();
+ fail();
+ } catch (FileNotFoundException e) { /* ok */ }
+
+ // The path may not be a symlink, neither on Darwin nor on Linux.
+ Path rootChild = testFS.getPath("/sbin");
+ if (!rootChild.isDirectory()) {
+ rootChild.createDirectory();
+ }
+ assertEquals(rootChild, linkPath.getRelative("sbin").resolveSymbolicLinks());
+ }
+
+ @Test
+ public void testLinkToFragmentContainingLinkResolvesCorrectly() throws IOException {
+ Path link1 = absolutize("link1");
+ PathFragment link1target = new PathFragment("link2/foo");
+ Path link2 = absolutize("link2");
+ Path link2target = xNonEmptyDirectory;
+
+ createSymbolicLink(link1, link1target); // ln -s link2/foo link1
+ createSymbolicLink(link2, link2target); // ln -s xNonEmptyDirectory link2
+ // link1 --> xNonEmptyDirectory/foo
+ assertEquals(link1.resolveSymbolicLinks(), link2target.getRelative("foo"));
+ }
+
+ //
+ // readSymbolicLink / resolveSymbolicLinks
+ //
+
+ @Test
+ public void testRecursiveSymbolicLink() throws IOException {
+ Path link = absolutize("recursive-link");
+ createSymbolicLink(link, link);
+
+ if (supportsSymlinks) {
+ try {
+ link.resolveSymbolicLinks();
+ fail();
+ } catch (IOException e) {
+ assertEquals(link + " (Too many levels of symbolic links)",
+ e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testMutuallyRecursiveSymbolicLinks() throws IOException {
+ Path link1 = absolutize("link1");
+ Path link2 = absolutize("link2");
+ createSymbolicLink(link2, link1);
+ createSymbolicLink(link1, link2);
+
+ if (supportsSymlinks) {
+ try {
+ link1.resolveSymbolicLinks();
+ fail();
+ } catch (IOException e) {
+ assertEquals(link1 + " (Too many levels of symbolic links)", e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testResolveSymbolicLinksENOENT() {
+ if (supportsSymlinks) {
+ try {
+ xDanglingLink.resolveSymbolicLinks();
+ fail();
+ } catch (IOException e) {
+ assertEquals(xNothing + " (No such file or directory)", e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testResolveSymbolicLinksENOTDIR() throws IOException {
+ if (supportsSymlinks) {
+ Path badLinkTarget = xFile.getChild("bad"); // parent is not a directory!
+ Path badLink = absolutize("badLink");
+ createSymbolicLink(badLink, badLinkTarget);
+ try {
+ badLink.resolveSymbolicLinks();
+ fail();
+ } catch (IOException e) {
+ // ok. Ideally we would assert "(Not a directory)" in the error
+ // message, but that would require yet another stat in the
+ // implementation.
+ }
+ }
+ }
+
+ @Test
+ public void testResolveSymbolicLinksWithUplevelRefs() throws IOException {
+ if (supportsSymlinks) {
+ // Create a series of links that refer to xFile as ./xFile,
+ // ./../foo/xFile, ./../../bar/foo/xFile, etc. They should all resolve
+ // to xFile.
+ Path ancestor = xFile;
+ String prefix = "./";
+ while ((ancestor = ancestor.getParentDirectory()) != null) {
+ xLinkToFile.delete();
+ createSymbolicLink(xLinkToFile, new PathFragment(prefix + xFile.relativeTo(ancestor)));
+ assertEquals(xFile, xLinkToFile.resolveSymbolicLinks());
+
+ prefix += "../";
+ }
+ }
+ }
+
+ @Test
+ public void testReadSymbolicLink() throws IOException {
+ if (supportsSymlinks) {
+ assertEquals(xNothing.toString(),
+ xDanglingLink.readSymbolicLink().toString());
+ }
+
+ assertEquals(xFile.toString(),
+ xLinkToFile.readSymbolicLink().toString());
+
+ assertEquals(xEmptyDirectory.toString(),
+ xLinkToDirectory.readSymbolicLink().toString());
+
+ try {
+ xFile.readSymbolicLink(); // not a link
+ fail();
+ } catch (NotASymlinkException e) {
+ assertEquals(xFile.toString(), e.getMessage());
+ }
+
+ try {
+ xNothing.readSymbolicLink(); // nothing there
+ fail();
+ } catch (IOException e) {
+ assertEquals(xNothing + " (No such file or directory)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testCannotCreateSymbolicLinkWithReadOnlyParent()
+ throws IOException {
+ xEmptyDirectory.setWritable(false);
+ Path xChildOfReadonlyDir = xEmptyDirectory.getChild("x");
+ if (supportsSymlinks) {
+ try {
+ xChildOfReadonlyDir.createSymbolicLink(xNothing);
+ fail();
+ } catch (IOException e) {
+ assertEquals(xChildOfReadonlyDir + " (Permission denied)", e.getMessage());
+ }
+ }
+ }
+
+ //
+ // createSymbolicLink
+ //
+
+ @Test
+ public void testCanCreateDanglingLink() throws IOException {
+ Path newPath = absolutize("non-existing-dir/new-file");
+ Path someLink = absolutize("dangling-link");
+ createSymbolicLink(someLink, newPath);
+ assertTrue(someLink.isSymbolicLink());
+ assertTrue(someLink.exists(Symlinks.NOFOLLOW)); // the link itself exists
+ assertFalse(someLink.exists()); // ...but the referent doesn't
+ if (supportsSymlinks) {
+ try {
+ someLink.resolveSymbolicLinks();
+ } catch (FileNotFoundException e) {
+ assertEquals(newPath.getParentDirectory()
+ + " (No such file or directory)", e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testCannotCreateSymbolicLinkWithoutParent() throws IOException {
+ Path xChildOfMissingDir = xNothing.getChild("x");
+ if (supportsSymlinks) {
+ try {
+ xChildOfMissingDir.createSymbolicLink(xFile);
+ fail();
+ } catch (FileNotFoundException e) {
+ MoreAsserts.assertEndsWith(" (No such file or directory)", e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testCreateSymbolicLinkWhereNothingExists() throws IOException {
+ createSymbolicLink(xNothing, xFile);
+ assertTrue(xNothing.isSymbolicLink());
+ }
+
+ @Test
+ public void testCreateSymbolicLinkWhereDirectoryAlreadyExists() {
+ try {
+ createSymbolicLink(xEmptyDirectory, xFile);
+ fail();
+ } catch (IOException e) { // => couldn't be created
+ assertEquals(xEmptyDirectory + " (File exists)", e.getMessage());
+ }
+ assertTrue(xEmptyDirectory.isDirectory(Symlinks.NOFOLLOW));
+ }
+
+ @Test
+ public void testCreateSymbolicLinkWhereFileAlreadyExists() {
+ try {
+ createSymbolicLink(xFile, xEmptyDirectory);
+ fail();
+ } catch (IOException e) { // => couldn't be created
+ assertEquals(xFile + " (File exists)", e.getMessage());
+ }
+ assertTrue(xFile.isFile(Symlinks.NOFOLLOW));
+ }
+
+ @Test
+ public void testCreateSymbolicLinkWhereDanglingSymlinkAlreadyExists() {
+ try {
+ createSymbolicLink(xDanglingLink, xFile);
+ fail();
+ } catch (IOException e) {
+ assertEquals(xDanglingLink + " (File exists)", e.getMessage());
+ }
+ assertTrue(xDanglingLink.isSymbolicLink()); // still a symbolic link
+ assertFalse(xDanglingLink.isDirectory()); // link still dangles
+ }
+
+ @Test
+ public void testCreateSymbolicLinkWhereSymlinkAlreadyExists() {
+ try {
+ createSymbolicLink(xLinkToDirectory, xNothing);
+ fail();
+ } catch (IOException e) {
+ assertEquals(xLinkToDirectory + " (File exists)", e.getMessage());
+ }
+ assertTrue(xLinkToDirectory.isSymbolicLink()); // still a symbolic link
+ assertTrue(xLinkToDirectory.isDirectory()); // link still points to dir
+ }
+
+ @Test
+ public void testDeleteLink() throws IOException {
+ Path newPath = xEmptyDirectory.getChild("new-file");
+ Path someLink = xEmptyDirectory.getChild("a-link");
+ FileSystemUtils.createEmptyFile(newPath);
+ createSymbolicLink(someLink, newPath);
+
+ assertEquals(xEmptyDirectory.getDirectoryEntries().size(), 2);
+
+ assertTrue(someLink.delete());
+ assertEquals(xEmptyDirectory.getDirectoryEntries().size(), 1);
+
+ assertThat(xEmptyDirectory.getDirectoryEntries()).containsExactly(newPath);
+ }
+
+ // Testing the links
+ @Test
+ public void testLinkFollowedToDirectory() throws IOException {
+ Path theDirectory = absolutize("foo/");
+ assertTrue(theDirectory.createDirectory());
+ Path newPath1 = absolutize("foo/new-file-1");
+ Path newPath2 = absolutize("foo/new-file-2");
+ Path newPath3 = absolutize("foo/new-file-3");
+
+ FileSystemUtils.createEmptyFile(newPath1);
+ FileSystemUtils.createEmptyFile(newPath2);
+ FileSystemUtils.createEmptyFile(newPath3);
+
+ Path linkPath = absolutize("link");
+ createSymbolicLink(linkPath, theDirectory);
+
+ Path resultPath1 = absolutize("link/new-file-1");
+ Path resultPath2 = absolutize("link/new-file-2");
+ Path resultPath3 = absolutize("link/new-file-3");
+ assertThat(linkPath.getDirectoryEntries()).containsExactly(resultPath1, resultPath2,
+ resultPath3);
+ }
+
+ @Test
+ public void testDanglingLinkIsNoFile() throws IOException {
+ Path newPath1 = absolutize("new-file-1");
+ Path newPath2 = absolutize("new-file-2");
+ FileSystemUtils.createEmptyFile(newPath1);
+ assertTrue(newPath2.createDirectory());
+
+ Path linkPath1 = absolutize("link1");
+ Path linkPath2 = absolutize("link2");
+ createSymbolicLink(linkPath1, newPath1);
+ createSymbolicLink(linkPath2, newPath2);
+
+ newPath1.delete();
+ newPath2.delete();
+
+ assertFalse(linkPath1.isFile());
+ assertFalse(linkPath2.isDirectory());
+ }
+
+ @Test
+ public void testWriteOnLinkChangesFile() throws IOException {
+ Path testFile = absolutize("test-file");
+ FileSystemUtils.createEmptyFile(testFile);
+ String testData = "abc19";
+
+ Path testLink = absolutize("a-link");
+ createSymbolicLink(testLink, testFile);
+
+ FileSystemUtils.writeContentAsLatin1(testLink, testData);
+ String resultData =
+ new String(FileSystemUtils.readContentAsLatin1(testFile));
+
+ assertEquals(testData,resultData);
+ }
+
+ //
+ // Symlink tests:
+ //
+
+ @Test
+ public void testExistsWithSymlinks() throws IOException {
+ Path a = absolutize("a");
+ Path b = absolutize("b");
+ FileSystemUtils.createEmptyFile(b);
+ createSymbolicLink(a, b); // ln -sf "b" "a"
+ assertTrue(a.exists()); // = exists(FOLLOW)
+ assertTrue(b.exists()); // = exists(FOLLOW)
+ assertTrue(a.exists(Symlinks.FOLLOW));
+ assertTrue(b.exists(Symlinks.FOLLOW));
+ assertTrue(a.exists(Symlinks.NOFOLLOW));
+ assertTrue(b.exists(Symlinks.NOFOLLOW));
+ b.delete(); // "a" is now a dangling link
+ assertFalse(a.exists()); // = exists(FOLLOW)
+ assertFalse(b.exists()); // = exists(FOLLOW)
+ assertFalse(a.exists(Symlinks.FOLLOW));
+ assertFalse(b.exists(Symlinks.FOLLOW));
+
+ assertTrue(a.exists(Symlinks.NOFOLLOW)); // symlink still exists
+ assertFalse(b.exists(Symlinks.NOFOLLOW));
+ }
+
+ @Test
+ public void testIsDirectoryWithSymlinks() throws IOException {
+ Path a = absolutize("a");
+ Path b = absolutize("b");
+ b.createDirectory();
+ createSymbolicLink(a, b); // ln -sf "b" "a"
+ assertTrue(a.isDirectory()); // = isDirectory(FOLLOW)
+ assertTrue(b.isDirectory()); // = isDirectory(FOLLOW)
+ assertTrue(a.isDirectory(Symlinks.FOLLOW));
+ assertTrue(b.isDirectory(Symlinks.FOLLOW));
+ assertFalse(a.isDirectory(Symlinks.NOFOLLOW)); // it's a link!
+ assertTrue(b.isDirectory(Symlinks.NOFOLLOW));
+ b.delete(); // "a" is now a dangling link
+ assertFalse(a.isDirectory()); // = isDirectory(FOLLOW)
+ assertFalse(b.isDirectory()); // = isDirectory(FOLLOW)
+ assertFalse(a.isDirectory(Symlinks.FOLLOW));
+ assertFalse(b.isDirectory(Symlinks.FOLLOW));
+ assertFalse(a.isDirectory(Symlinks.NOFOLLOW));
+ assertFalse(b.isDirectory(Symlinks.NOFOLLOW));
+ }
+
+ @Test
+ public void testIsFileWithSymlinks() throws IOException {
+ Path a = absolutize("a");
+ Path b = absolutize("b");
+ FileSystemUtils.createEmptyFile(b);
+ createSymbolicLink(a, b); // ln -sf "b" "a"
+ assertTrue(a.isFile()); // = isFile(FOLLOW)
+ assertTrue(b.isFile()); // = isFile(FOLLOW)
+ assertTrue(a.isFile(Symlinks.FOLLOW));
+ assertTrue(b.isFile(Symlinks.FOLLOW));
+ assertFalse(a.isFile(Symlinks.NOFOLLOW)); // it's a link!
+ assertTrue(b.isFile(Symlinks.NOFOLLOW));
+ b.delete(); // "a" is now a dangling link
+ assertFalse(a.isFile()); // = isFile()
+ assertFalse(b.isFile()); // = isFile()
+ assertFalse(a.isFile());
+ assertFalse(b.isFile());
+ assertFalse(a.isFile(Symlinks.NOFOLLOW));
+ assertFalse(b.isFile(Symlinks.NOFOLLOW));
+ }
+
+ @Test
+ public void testGetDirectoryEntriesOnLinkToDirectory() throws Exception {
+ Path fooAlias = xNothing.getChild("foo");
+ createSymbolicLink(xNothing, xNonEmptyDirectory);
+ Collection<Path> dirents = xNothing.getDirectoryEntries();
+ assertThat(dirents).containsExactly(fooAlias);
+ }
+
+ @Test
+ public void testFilesOfLinkedDirectories() throws Exception {
+ Path child = xEmptyDirectory.getChild("child");
+ Path aliasToChild = xLinkToDirectory.getChild("child");
+
+ assertFalse(aliasToChild.exists());
+ FileSystemUtils.createEmptyFile(child);
+ assertTrue(aliasToChild.exists());
+ assertTrue(aliasToChild.isFile());
+ assertFalse(aliasToChild.isDirectory());
+
+ validateLinkedReferenceObeysReadOnly(child, aliasToChild);
+ validateLinkedReferenceObeysExecutable(child, aliasToChild);
+ }
+
+ @Test
+ public void testDirectoriesOfLinkedDirectories() throws Exception {
+ Path childDir = xEmptyDirectory.getChild("childDir");
+ Path linkToChildDir = xLinkToDirectory.getChild("childDir");
+
+ assertFalse(linkToChildDir.exists());
+ childDir.createDirectory();
+ assertTrue(linkToChildDir.exists());
+ assertTrue(linkToChildDir.isDirectory());
+ assertFalse(linkToChildDir.isFile());
+
+ validateLinkedReferenceObeysReadOnly(childDir, linkToChildDir);
+ validateLinkedReferenceObeysExecutable(childDir, linkToChildDir);
+ }
+
+ @Test
+ public void testDirectoriesOfLinkedDirectoriesOfLinkedDirectories() throws Exception {
+ Path childDir = xEmptyDirectory.getChild("childDir");
+ Path linkToLinkToDirectory = absolutize("xLinkToLinkToDirectory");
+ createSymbolicLink(linkToLinkToDirectory, xLinkToDirectory);
+ Path linkToChildDir = linkToLinkToDirectory.getChild("childDir");
+
+ assertFalse(linkToChildDir.exists());
+ childDir.createDirectory();
+ assertTrue(linkToChildDir.exists());
+ assertTrue(linkToChildDir.isDirectory());
+ assertFalse(linkToChildDir.isFile());
+
+ validateLinkedReferenceObeysReadOnly(childDir, linkToChildDir);
+ validateLinkedReferenceObeysExecutable(childDir, linkToChildDir);
+ }
+
+ private void validateLinkedReferenceObeysReadOnly(Path path, Path link) throws IOException {
+ path.setWritable(false);
+ assertFalse(path.isWritable());
+ assertFalse(link.isWritable());
+ path.setWritable(true);
+ assertTrue(path.isWritable());
+ assertTrue(link.isWritable());
+ path.setWritable(false);
+ assertFalse(path.isWritable());
+ assertFalse(link.isWritable());
+ }
+
+ private void validateLinkedReferenceObeysExecutable(Path path, Path link) throws IOException {
+ path.setExecutable(true);
+ assertTrue(path.isExecutable());
+ assertTrue(link.isExecutable());
+ path.setExecutable(false);
+ assertFalse(path.isExecutable());
+ assertFalse(link.isExecutable());
+ path.setExecutable(true);
+ assertTrue(path.isExecutable());
+ assertTrue(link.isExecutable());
+ }
+
+ @Test
+ public void testReadingFileFromLinkedDirectory() throws Exception {
+ Path linkedTo = absolutize("linkedTo");
+ linkedTo.createDirectory();
+ Path child = linkedTo.getChild("child");
+ FileSystemUtils.createEmptyFile(child);
+
+ byte[] outputData = "This is a test".getBytes();
+ FileSystemUtils.writeContent(child, outputData);
+
+ Path link = absolutize("link");
+ createSymbolicLink(link, linkedTo);
+ Path linkedChild = link.getChild("child");
+ byte[] inputData = FileSystemUtils.readContent(linkedChild);
+ assertArrayEquals(outputData, inputData);
+ }
+
+ @Test
+ public void testCreatingFileInLinkedDirectory() throws Exception {
+ Path linkedTo = absolutize("linkedTo");
+ linkedTo.createDirectory();
+ Path child = linkedTo.getChild("child");
+
+ Path link = absolutize("link");
+ createSymbolicLink(link, linkedTo);
+ Path linkedChild = link.getChild("child");
+ byte[] outputData = "This is a test".getBytes();
+ FileSystemUtils.writeContent(linkedChild, outputData);
+
+ byte[] inputData = FileSystemUtils.readContent(child);
+ assertArrayEquals(outputData, inputData);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java
new file mode 100644
index 0000000000..396a9f8441
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnionFileSystemTest.java
@@ -0,0 +1,330 @@
+// Copyright 2014 Google Inc. 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 static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.util.Clock;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Tests for the UnionFileSystem, both of generic FileSystem functionality
+ * (inherited) and tests of UnionFileSystem-specific behavior.
+ */
+@RunWith(JUnit4.class)
+public class UnionFileSystemTest extends SymlinkAwareFileSystemTest {
+ private XAttrInMemoryFs inDelegate;
+ private XAttrInMemoryFs outDelegate;
+ private XAttrInMemoryFs defaultDelegate;
+ private UnionFileSystem unionfs;
+
+ private static final String XATTR_VAL = "SOME_XATTR_VAL";
+ private static final String XATTR_KEY = "SOME_XATTR_KEY";
+
+ private void setupDelegateFileSystems() {
+ inDelegate = new XAttrInMemoryFs(BlazeClock.instance());
+ outDelegate = new XAttrInMemoryFs(BlazeClock.instance());
+ defaultDelegate = new XAttrInMemoryFs(BlazeClock.instance());
+
+ unionfs = createDefaultUnionFileSystem();
+ }
+
+ private UnionFileSystem createDefaultUnionFileSystem() {
+ return createDefaultUnionFileSystem(false);
+ }
+
+ private UnionFileSystem createDefaultUnionFileSystem(boolean readOnly) {
+ return new UnionFileSystem(ImmutableMap.<PathFragment, FileSystem>of(
+ new PathFragment("/in"), inDelegate,
+ new PathFragment("/out"), outDelegate),
+ defaultDelegate, readOnly);
+ }
+
+ @Override
+ protected FileSystem getFreshFileSystem() {
+ // Executed with each new test because it is called by super.setUp().
+ setupDelegateFileSystems();
+ return unionfs;
+ }
+
+ @Override
+ public void destroyFileSystem(FileSystem fileSystem) {
+ // Nothing.
+ }
+
+ // Tests of UnionFileSystem-specific behavior below.
+
+ @Test
+ public void testBasicDelegation() throws Exception {
+ unionfs = createDefaultUnionFileSystem();
+ Path fooPath = unionfs.getPath("/foo");
+ Path inPath = unionfs.getPath("/in");
+ Path outPath = unionfs.getPath("/out/in.txt");
+ assertSame(inDelegate, unionfs.getDelegate(inPath));
+ assertSame(outDelegate, unionfs.getDelegate(outPath));
+ assertSame(defaultDelegate, unionfs.getDelegate(fooPath));
+ }
+
+ @Test
+ public void testBasicXattr() throws Exception {
+ Path fooPath = unionfs.getPath("/foo");
+ Path inPath = unionfs.getPath("/in");
+ Path outPath = unionfs.getPath("/out/in.txt");
+
+ assertArrayEquals(XATTR_VAL.getBytes(UTF_8), inPath.getxattr(XATTR_KEY));
+ assertArrayEquals(XATTR_VAL.getBytes(UTF_8), outPath.getxattr(XATTR_KEY));
+ assertArrayEquals(XATTR_VAL.getBytes(UTF_8), fooPath.getxattr(XATTR_KEY));
+ assertNull(inPath.getxattr("not_key"));
+ assertNull(outPath.getxattr("not_key"));
+ assertNull(fooPath.getxattr("not_key"));
+ }
+
+ @Test
+ public void testDefaultFileSystemRequired() throws Exception {
+ try {
+ new UnionFileSystem(ImmutableMap.<PathFragment, FileSystem>of(), null);
+ fail("Able to create a UnionFileSystem with no default!");
+ } catch (NullPointerException expected) {
+ // OK - should fail in this case.
+ }
+ }
+
+ // Check for appropriate registration and lookup of delegate filesystems based
+ // on path prefixes, including non-canonical paths.
+ @Test
+ public void testPrefixDelegation() throws Exception {
+ unionfs = new UnionFileSystem(ImmutableMap.<PathFragment, FileSystem>of(
+ new PathFragment("/foo"), inDelegate,
+ new PathFragment("/foo/bar"), outDelegate), defaultDelegate);
+
+ assertSame(inDelegate, unionfs.getDelegate(unionfs.getPath("/foo/foo.txt")));
+ assertSame(outDelegate, unionfs.getDelegate(unionfs.getPath("/foo/bar/foo.txt")));
+ assertSame(inDelegate, unionfs.getDelegate(unionfs.getPath("/foo/bar/../foo.txt")));
+ assertSame(defaultDelegate, unionfs.getDelegate(unionfs.getPath("/bar/foo.txt")));
+ assertSame(defaultDelegate, unionfs.getDelegate(unionfs.getPath("/foo/bar/../..")));
+ }
+
+ // Checks that files cannot be modified when the filesystem is created
+ // read-only, even if the delegate filesystems are read/write.
+ @Test
+ public void testModificationFlag() throws Exception {
+ assertTrue(unionfs.supportsModifications());
+ Path outPath = unionfs.getPath("/out/foo.txt");
+ assertTrue(unionfs.createDirectory(outPath.getParentDirectory()));
+ OutputStream outFile = unionfs.getOutputStream(outPath);
+ outFile.write('b');
+ outFile.close();
+
+ unionfs.setExecutable(outPath, true);
+
+ // Note that this does not destroy the underlying filesystems;
+ // UnionFileSystem is just a view.
+ unionfs = createDefaultUnionFileSystem(true);
+ assertFalse(unionfs.supportsModifications());
+
+ InputStream outFileInput = unionfs.getInputStream(outPath);
+ int outFileByte = outFileInput.read();
+ outFileInput.close();
+ assertEquals('b', outFileByte);
+
+ assertTrue(unionfs.isExecutable(outPath));
+
+ // Modifying files through the unionfs isn't permitted, even if the
+ // delegates are read/write.
+ try {
+ unionfs.setExecutable(outPath, false);
+ fail("Modification to a read-only UnionFileSystem succeeded.");
+ } catch (UnsupportedOperationException expected) {
+ // OK - should fail.
+ }
+ }
+
+ // Checks that roots of delegate filesystems are created outside of the
+ // delegate filesystems; i.e. they can be seen from the filesystem of the parent.
+ @Test
+ public void testDelegateRootDirectoryCreation() throws Exception {
+ Path foo = unionfs.getPath("/foo");
+ Path bar = unionfs.getPath("/bar");
+ Path out = unionfs.getPath("/out");
+ assertTrue(unionfs.createDirectory(foo));
+ assertTrue(unionfs.createDirectory(bar));
+ assertTrue(unionfs.createDirectory(out));
+ Path outFile = unionfs.getPath("/out/in");
+ FileSystemUtils.writeContentAsLatin1(outFile, "Out");
+
+ // FileSystemTest.setUp() silently creates the test root on the filesystem...
+ Path testDirUnderRoot = unionfs.getPath(workingDir.asFragment().subFragment(0, 1));
+ assertThat(unionfs.getDirectoryEntries(unionfs.getRootDirectory())).containsExactly(foo, bar,
+ out, testDirUnderRoot);
+ assertThat(unionfs.getDirectoryEntries(out)).containsExactly(outFile);
+
+ assertSame(unionfs.getDelegate(foo), defaultDelegate);
+ assertEquals(foo.asFragment(), unionfs.adjustPath(foo, defaultDelegate).asFragment());
+ assertSame(unionfs.getDelegate(bar), defaultDelegate);
+ assertSame(unionfs.getDelegate(outFile), outDelegate);
+ assertSame(unionfs.getDelegate(out), outDelegate);
+
+ // As a fragment (i.e. without filesystem or root info), the path name should be preserved.
+ assertEquals(outFile.asFragment(), unionfs.adjustPath(outFile, outDelegate).asFragment());
+ }
+
+ // Ensure that the right filesystem is still chosen when paths contain "..".
+ @Test
+ public void testDelegationOfUpLevelReferences() throws Exception {
+ assertSame(defaultDelegate, unionfs.getDelegate(unionfs.getPath("/in/../foo.txt")));
+ assertSame(inDelegate, unionfs.getDelegate(unionfs.getPath("/out/../in")));
+ assertSame(outDelegate, unionfs.getDelegate(unionfs.getPath("/out/../in/../out/foo.txt")));
+ assertSame(inDelegate, unionfs.getDelegate(unionfs.getPath("/in/./foo.txt")));
+ }
+
+ // Basic *explicit* cross-filesystem symlink check.
+ // Note: This does not work implicitly yet, as the next test illustrates.
+ @Test
+ public void testCrossDeviceSymlinks() throws Exception {
+ assertTrue(unionfs.createDirectory(unionfs.getPath("/out")));
+
+ // Create an "/in" directory directly on the output delegate to bypass the
+ // UnionFileSystem's mapping.
+ assertTrue(inDelegate.getPath("/in").createDirectory());
+ OutputStream outStream = inDelegate.getPath("/in/bar.txt").getOutputStream();
+ outStream.write('i');
+ outStream.close();
+
+ Path outFoo = unionfs.getPath("/out/foo");
+ unionfs.createSymbolicLink(outFoo, new PathFragment("../in/bar.txt"));
+ assertTrue(unionfs.stat(outFoo, false).isSymbolicLink());
+
+ try {
+ unionfs.stat(outFoo, true).isFile();
+ fail("Stat on cross-device symlink succeeded!");
+ } catch (FileNotFoundException expected) {
+ // OK
+ }
+
+ Path resolved = unionfs.resolveSymbolicLinks(outFoo);
+ assertSame(unionfs, resolved.getFileSystem());
+ InputStream barInput = resolved.getInputStream();
+ int barChar = barInput.read();
+ barInput.close();
+ assertEquals('i', barChar);
+ }
+
+ @Test
+ public void testNoDelegateLeakage() throws Exception {
+ assertSame(unionfs, unionfs.getPath("/in/foo.txt").getFileSystem());
+ assertSame(unionfs, unionfs.getPath("/in/foo/bar").getParentDirectory().getFileSystem());
+ unionfs.createDirectory(unionfs.getPath("/out"));
+ unionfs.createDirectory(unionfs.getPath("/out/foo"));
+ unionfs.createDirectory(unionfs.getPath("/out/foo/bar"));
+ assertSame(unionfs, Iterables.getOnlyElement(unionfs.getDirectoryEntries(
+ unionfs.getPath("/out/foo"))).getParentDirectory().getFileSystem());
+ }
+
+ // Prefix mappings can apply to files starting with a prefix within a directory.
+ @Test
+ public void testWithinDirectoryMapping() throws Exception {
+ unionfs = new UnionFileSystem(ImmutableMap.<PathFragment, FileSystem>of(
+ new PathFragment("/fruit/a"), inDelegate,
+ new PathFragment("/fruit/b"), outDelegate), defaultDelegate);
+ assertTrue(unionfs.createDirectory(unionfs.getPath("/fruit")));
+ assertTrue(defaultDelegate.getPath("/fruit").isDirectory());
+ assertTrue(inDelegate.getPath("/fruit").createDirectory());
+ assertTrue(outDelegate.getPath("/fruit").createDirectory());
+
+ Path apple = unionfs.getPath("/fruit/apple");
+ Path banana = unionfs.getPath("/fruit/banana");
+ Path cherry = unionfs.getPath("/fruit/cherry");
+ unionfs.createDirectory(apple);
+ unionfs.createDirectory(banana);
+ assertSame(inDelegate, unionfs.getDelegate(apple));
+ assertSame(outDelegate, unionfs.getDelegate(banana));
+ assertSame(defaultDelegate, unionfs.getDelegate(cherry));
+
+ FileSystemUtils.writeContentAsLatin1(apple.getRelative("table"), "penny");
+ FileSystemUtils.writeContentAsLatin1(banana.getRelative("nana"), "nanana");
+ FileSystemUtils.writeContentAsLatin1(cherry, "garcia");
+
+ assertEquals("penny", new String(
+ FileSystemUtils.readContentAsLatin1(inDelegate.getPath("/fruit/apple/table"))));
+ assertEquals("nanana", new String(
+ FileSystemUtils.readContentAsLatin1(outDelegate.getPath("/fruit/banana/nana"))));
+ assertEquals("garcia", new String(
+ FileSystemUtils.readContentAsLatin1(defaultDelegate.getPath("/fruit/cherry"))));
+ }
+
+ // Write using the VFS through a UnionFileSystem and check that the file can
+ // be read back in the same location using standard Java IO.
+ // There is a similar test in UnixFileSystem, but this is essential to ensure
+ // that paths aren't being remapped in some nasty way on the underlying FS.
+ @Test
+ public void testDelegateOperationsReflectOnLocalFilesystem() throws Exception {
+ unionfs = new UnionFileSystem(ImmutableMap.<PathFragment, FileSystem>of(
+ workingDir.getParentDirectory().asFragment(), new UnixFileSystem()),
+ defaultDelegate, false);
+ // This is a child of the current tmpdir, and doesn't exist on its own.
+ // It would be created in setup(), but of course, that didn't use a UnixFileSystem.
+ unionfs.createDirectory(workingDir);
+ Path testFile = unionfs.getPath(workingDir.getRelative("test_file").asFragment());
+ assertTrue(testFile.asFragment().startsWith(workingDir.asFragment()));
+ String testString = "This is a test file";
+ FileSystemUtils.writeContentAsLatin1(testFile, testString);
+ try {
+ assertEquals(testString, new String(FileSystemUtils.readContentAsLatin1(testFile)));
+ } finally {
+ testFile.delete();
+ assertTrue(unionfs.delete(workingDir));
+ }
+ }
+
+ // Regression test for [UnionFS: Directory creation across mapping fails.]
+ @Test
+ public void testCreateParentsAcrossMapping() throws Exception {
+ unionfs = new UnionFileSystem(ImmutableMap.<PathFragment, FileSystem>of(
+ new PathFragment("/out/dir"), outDelegate), defaultDelegate, false);
+ Path outDir = unionfs.getPath("/out/dir/biz/bang");
+ FileSystemUtils.createDirectoryAndParents(outDir);
+ assertTrue(outDir.isDirectory());
+ }
+
+ private static class XAttrInMemoryFs extends InMemoryFileSystem {
+ public XAttrInMemoryFs(Clock clock) {
+ super(clock);
+ }
+
+ @Override
+ protected byte[] getxattr(Path path, String name, boolean followSymlinks) {
+ assertSame(this, path.getFileSystem());
+ return (name.equals(XATTR_KEY)) ? XATTR_VAL.getBytes(UTF_8) : null;
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java
new file mode 100644
index 0000000000..0ded4048a6
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnixFileSystemTest.java
@@ -0,0 +1,63 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+/**
+ * Tests for the {@link UnixFileSystem} class.
+ */
+@RunWith(JUnit4.class)
+public class UnixFileSystemTest extends SymlinkAwareFileSystemTest {
+
+ @Override
+ protected FileSystem getFreshFileSystem() {
+ return new UnixFileSystem();
+ }
+
+ @Override
+ public void destroyFileSystem(FileSystem fileSystem) {
+ // Nothing.
+ }
+
+ @Override
+ protected void expectNotFound(Path path) throws IOException {
+ assertNull(path.statIfFound());
+ }
+
+ // Most tests are just inherited from FileSystemTest.
+
+ @Test
+ public void testCircularSymlinkFound() throws Exception {
+ Path linkA = absolutize("link-a");
+ Path linkB = absolutize("link-b");
+ linkA.createSymbolicLink(linkB);
+ linkB.createSymbolicLink(linkA);
+ assertFalse(linkA.exists(Symlinks.FOLLOW));
+ try {
+ linkA.statIfFound(Symlinks.FOLLOW);
+ fail();
+ } catch (IOException expected) {
+ // Expected.
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnixPathEqualityTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathEqualityTest.java
new file mode 100644
index 0000000000..f5f58e2c7e
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathEqualityTest.java
@@ -0,0 +1,118 @@
+// Copyright 2014 Google Inc. 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * This tests how canonical paths and non-canonical paths are equal with each
+ * other, and also how paths from different filesystems behave with each other.
+ */
+@RunWith(JUnit4.class)
+public class UnixPathEqualityTest {
+
+ private FileSystem otherUnixFs;
+ private FileSystem unixFs;
+
+ @Before
+ public void setUp() throws Exception {
+ unixFs = new UnixFileSystem();
+ otherUnixFs = new UnixFileSystem();
+ assertTrue(unixFs != otherUnixFs);
+ }
+
+ private void assertTwoWayEquals(Object obj1, Object obj2) {
+ assertTrue(obj1.equals(obj2));
+ assertTrue(obj2.equals(obj1));
+ assertEquals(obj1.hashCode(), obj2.hashCode());
+ }
+
+ private void assertTwoWayNotEquals(Object obj1, Object obj2) {
+ assertFalse(obj1.equals(obj2));
+ assertFalse(obj2.equals(obj1));
+ }
+
+ @Test
+ public void testPathsAreEqualEvenIfNotCanonical() {
+ // This path is already canonical, so there's no difference between
+ // the canonical / nonCanonical path, as far as equals is concerned
+ Path nonCanonical = unixFs.getPath("/a/canonical/unix/path");
+ Path canonical = unixFs.getPath("/a/canonical/unix/path");
+ assertTwoWayEquals(nonCanonical, canonical);
+ }
+
+ @Test
+ public void testPathsAreNeverEqualWithStrings() {
+ // Make sure that paths aren't equal to plain old strings
+ Path nonCanonical = unixFs.getPath("/a/non/../canonical/unix/path");
+ Path canonical = unixFs.getPath("/a/non/../canonical/unix/path");
+ assertTwoWayNotEquals(nonCanonical, "/a/non/../canonical/unix/path");
+ assertTwoWayNotEquals(canonical, "/a/non/../canonical/unix/path");
+ }
+
+ @Test
+ public void testCanonicalPathsFromDifferentFileSystemsAreNeverEqual() {
+ Path canonical = unixFs.getPath("/canonical/path");
+ Path otherCanonical = otherUnixFs.getPath("/canonical/path");
+ assertTwoWayNotEquals(canonical, otherCanonical);
+ }
+
+ @Test
+ public void testNonCanonicalPathsFromDifferentFileSystemsAreNeverEqual() {
+ Path nonCanonical = unixFs.getPath("/non/canonical/path");
+ Path otherNonCanonical = otherUnixFs.getPath("/non/canonical/path");
+ assertTwoWayNotEquals(nonCanonical, otherNonCanonical);
+ }
+
+ @Test
+ public void testCrossFilesystemStartsWithReturnsFalse() {
+ assertFalse(unixFs.getPath("/a").startsWith(otherUnixFs.getPath("/b")));
+ }
+
+ @Test
+ public void testCrossFilesystemOperationsForbidden() throws Exception {
+ Path a = unixFs.getPath("/a");
+ Path b = otherUnixFs.getPath("/b");
+
+ try {
+ a.renameTo(b);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).contains("different filesystems");
+ }
+
+ try {
+ a.relativeTo(b);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).contains("different filesystems");
+ }
+
+ try {
+ a.createSymbolicLink(b);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e.getMessage()).contains("different filesystems");
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnixPathGetParentTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathGetParentTest.java
new file mode 100644
index 0000000000..0f679c3e78
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathGetParentTest.java
@@ -0,0 +1,87 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertEquals;
+
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.vfs.util.FileSystems;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+/**
+ * A test for {@link Path} in the context of {@link UnixFileSystem}.
+ */
+@RunWith(JUnit4.class)
+public class UnixPathGetParentTest {
+
+ private FileSystem unixFs;
+ private Path testRoot;
+
+ @Before
+ public void setUp() throws Exception {
+ unixFs = FileSystems.initDefaultAsNative();
+ testRoot = unixFs.getPath(TestUtils.tmpDir()).getRelative("UnixPathGetParentTest");
+ FileSystemUtils.createDirectoryAndParents(testRoot);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileSystemUtils.deleteTree(testRoot); // (comment out during debugging)
+ }
+
+ private Path getParent(String path) {
+ return unixFs.getPath(path).getParentDirectory();
+ }
+
+ @Test
+ public void testAbsoluteRootHasNoParent() {
+ assertEquals(null, getParent("/"));
+ }
+
+ @Test
+ public void testParentOfSimpleDirectory() {
+ assertEquals("/foo", getParent("/foo/bar").getPathString());
+ }
+
+ @Test
+ public void testParentOfDotDotInMiddleOfPathname() {
+ assertEquals("/", getParent("/foo/../bar").getPathString());
+ }
+
+ @Test
+ public void testGetPathDoesNormalizationWithoutIO() throws IOException {
+ Path tmp = testRoot.getChild("tmp");
+ Path tmpWiz = tmp.getChild("wiz");
+
+ tmp.createDirectory();
+
+ // ln -sf /tmp /tmp/wiz
+ tmpWiz.createSymbolicLink(tmp);
+
+ assertEquals(testRoot, tmp.getParentDirectory());
+
+ assertEquals(tmp, tmpWiz.getParentDirectory());
+
+ // Under UNIX, inode(/tmp/wiz/..) == inode(/). However getPath() does not
+ // perform I/O, only string operations, so it disagrees:
+ assertEquals(tmp, tmp.getRelative(new PathFragment("wiz/..")));
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java
new file mode 100644
index 0000000000..b59336725a
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/UnixPathTest.java
@@ -0,0 +1,279 @@
+// Copyright 2014 Google Inc. 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.vfs.util.FileSystems;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Tests for {@link Path}.
+ */
+@RunWith(JUnit4.class)
+public class UnixPathTest {
+
+ private FileSystem unixFs;
+ private File aDirectory;
+ private File aFile;
+ private File anotherFile;
+ private File tmpDir;
+
+ protected FileSystem getUnixFileSystem() {
+ return FileSystems.initDefaultAsNative();
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ unixFs = getUnixFileSystem();
+ tmpDir = new File(TestUtils.tmpDir(), "tmpDir");
+ tmpDir.mkdirs();
+ aDirectory = new File(tmpDir, "a_directory");
+ aDirectory.mkdirs();
+ aFile = new File(tmpDir, "a_file");
+ new FileWriter(aFile).close();
+ anotherFile = new File(aDirectory, "another_file.txt");
+ new FileWriter(anotherFile).close();
+ }
+
+ @Test
+ public void testExists() {
+ assertTrue(unixFs.getPath(aDirectory.getPath()).exists());
+ assertTrue(unixFs.getPath(aFile.getPath()).exists());
+ assertFalse(unixFs.getPath("/does/not/exist").exists());
+ }
+
+ @Test
+ public void testDirectoryEntriesForDirectory() throws IOException {
+ Collection<Path> entries =
+ unixFs.getPath(tmpDir.getPath()).getDirectoryEntries();
+ List<Path> expectedEntries = Arrays.asList(
+ unixFs.getPath(tmpDir.getPath() + "/a_file"),
+ unixFs.getPath(tmpDir.getPath() + "/a_directory"));
+
+ assertEquals(new HashSet<Object>(expectedEntries),
+ new HashSet<Object>(entries));
+ }
+
+ @Test
+ public void testDirectoryEntriesForFileThrowsException() {
+ try {
+ unixFs.getPath(aFile.getPath()).getDirectoryEntries();
+ fail("No exception thrown.");
+ } catch (IOException x) {
+ // The expected result.
+ }
+ }
+
+ @Test
+ public void testIsFileIsTrueForFile() {
+ assertTrue(unixFs.getPath(aFile.getPath()).isFile());
+ }
+
+ @Test
+ public void testIsFileIsFalseForDirectory() {
+ assertFalse(unixFs.getPath(aDirectory.getPath()).isFile());
+ }
+
+ @Test
+ public void testBaseName() {
+ assertEquals("base", unixFs.getPath("/foo/base").getBaseName());
+ }
+
+ @Test
+ public void testBaseNameRunsAfterDotDotInterpretation() {
+ assertEquals("base", unixFs.getPath("/base/foo/..").getBaseName());
+ }
+
+ @Test
+ public void testParentOfRootIsRoot() {
+ assertEquals(unixFs.getPath("/"), unixFs.getPath("/.."));
+ assertEquals(unixFs.getPath("/"), unixFs.getPath("/../../../../../.."));
+ assertEquals(unixFs.getPath("/foo"), unixFs.getPath("/../../../foo"));
+ }
+
+ @Test
+ public void testIsDirectory() {
+ assertTrue(unixFs.getPath(aDirectory.getPath()).isDirectory());
+ assertFalse(unixFs.getPath(aFile.getPath()).isDirectory());
+ assertFalse(unixFs.getPath("/does/not/exist").isDirectory());
+ }
+
+ @Test
+ public void testListNonExistingDirectoryThrowsException() {
+ try {
+ unixFs.getPath("/does/not/exist").getDirectoryEntries();
+ fail("No exception thrown.");
+ } catch (IOException ex) {
+ // success!
+ }
+ }
+
+ private void assertPathSet(Collection<Path> actual, String... expected) {
+ List<String> actualStrings = Lists.newArrayListWithCapacity(actual.size());
+
+ for (Path path : actual) {
+ actualStrings.add(path.getPathString());
+ }
+
+ assertThat(actualStrings).containsExactlyElementsIn(Arrays.asList(expected));
+ }
+
+ @Test
+ public void testGlob() throws Exception {
+ Collection<Path> textFiles = UnixGlob.forPath(unixFs.getPath(tmpDir.getPath()))
+ .addPattern("*/*.txt")
+ .globInterruptible();
+ assertEquals(1, textFiles.size());
+ Path onlyFile = textFiles.iterator().next();
+ assertEquals(unixFs.getPath(anotherFile.getPath()), onlyFile);
+
+ Collection<Path> onlyFiles =
+ UnixGlob.forPath(unixFs.getPath(tmpDir.getPath()))
+ .addPattern("*")
+ .setExcludeDirectories(true)
+ .globInterruptible();
+ assertPathSet(onlyFiles, aFile.getPath());
+
+ Collection<Path> directoriesToo =
+ UnixGlob.forPath(unixFs.getPath(tmpDir.getPath()))
+ .addPattern("*")
+ .setExcludeDirectories(false)
+ .globInterruptible();
+ assertPathSet(directoriesToo, aFile.getPath(), aDirectory.getPath());
+ }
+
+ @Test
+ public void testGetRelative() {
+ Path relative = unixFs.getPath("/foo").getChild("bar");
+ Path expected = unixFs.getPath("/foo/bar");
+ assertEquals(expected, relative);
+ }
+
+ @Test
+ public void testEqualsAndHash() {
+ Path path = unixFs.getPath("/foo/bar");
+ Path equalPath = unixFs.getPath("/foo/bar");
+ Path differentPath = unixFs.getPath("/foo/bar/baz");
+ Object differentType = new Object();
+
+ assertEquals(path.hashCode(), equalPath.hashCode());
+ assertEquals(path, equalPath);
+ assertFalse(path.equals(differentPath));
+ assertFalse(path.equals(differentType));
+ }
+
+ @Test
+ public void testLatin1ReadAndWrite() throws IOException {
+ char[] allLatin1Chars = new char[256];
+ for (int i = 0; i < 256; i++) {
+ allLatin1Chars[i] = (char) i;
+ }
+ Path path = unixFs.getPath(aFile.getPath());
+ String latin1String = new String(allLatin1Chars);
+ FileSystemUtils.writeContentAsLatin1(path, latin1String);
+ String fileContent = new String(FileSystemUtils.readContentAsLatin1(path));
+ assertEquals(fileContent, latin1String);
+ }
+
+ /**
+ * Verify that the encoding implemented by
+ * {@link FileSystemUtils#writeContentAsLatin1(Path, String)}
+ * really is 8859-1 (latin1).
+ */
+ @Test
+ public void testVerifyLatin1() throws IOException {
+ char[] allLatin1Chars = new char[256];
+ for( int i = 0; i < 256; i++) {
+ allLatin1Chars[i] = (char)i;
+ }
+ Path path = unixFs.getPath(aFile.getPath());
+ String latin1String = new String(allLatin1Chars);
+ FileSystemUtils.writeContentAsLatin1(path, latin1String);
+ byte[] bytes = FileSystemUtils.readContent(path);
+ assertEquals(new String(bytes, "ISO-8859-1"), latin1String);
+ }
+
+ @Test
+ public void testBytesReadAndWrite() throws IOException {
+ byte[] bytes = new byte[] { (byte) 0xdeadbeef, (byte) 0xdeadbeef>>8,
+ (byte) 0xdeadbeef>>16, (byte) 0xdeadbeef>>24 };
+ Path path = unixFs.getPath(aFile.getPath());
+ FileSystemUtils.writeContent(path, bytes);
+ byte[] content = FileSystemUtils.readContent(path);
+ assertEquals(bytes.length, content.length);
+ for (int i = 0; i < bytes.length; i++) {
+ assertEquals(bytes[i], content[i]);
+ }
+ }
+
+ @Test
+ public void testInputOutputStreams() throws IOException {
+ Path path = unixFs.getPath(aFile.getPath());
+ OutputStream out = path.getOutputStream();
+ for (int i = 0; i < 256; i++) {
+ out.write(i);
+ }
+ out.close();
+ InputStream in = path.getInputStream();
+ for (int i = 0; i < 256; i++) {
+ assertEquals(i, in.read());
+ }
+ assertEquals(-1, in.read());
+ in.close();
+ }
+
+ @Test
+ public void testAbsolutePathRoot() {
+ assertEquals("/", new Path(null).toString());
+ }
+
+ @Test
+ public void testAbsolutePath() {
+ Path segment = new Path(null, "bar.txt",
+ new Path(null, "foo", new Path(null)));
+ assertEquals("/foo/bar.txt", segment.toString());
+ }
+
+ @Test
+ public void testDerivedSegmentEquality() {
+ Path absoluteSegment = new Path(null);
+
+ Path derivedNode = absoluteSegment.getChild("derivedSegment");
+ Path otherDerivedNode = absoluteSegment.getChild("derivedSegment");
+
+ assertSame(derivedNode, otherDerivedNode);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/ZipFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/ZipFileSystemTest.java
new file mode 100644
index 0000000000..9dc12764ae
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/ZipFileSystemTest.java
@@ -0,0 +1,233 @@
+// Copyright 2014 Google Inc. 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Lists;
+import com.google.common.io.CharStreams;
+import com.google.devtools.build.lib.testutil.BlazeTestUtils;
+import com.google.devtools.build.lib.testutil.TestConstants;
+import com.google.devtools.build.lib.vfs.util.FileSystems;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class ZipFileSystemTest {
+
+ /**
+ * Expected listing of sample zip files, in alpha sorted order
+ */
+ private static final String[] LISTING = {
+ "/dir1",
+ "/dir1/file1a",
+ "/dir1/file1b",
+ "/dir2",
+ "/dir2/dir3",
+ "/dir2/dir3/dir4",
+ "/dir2/dir3/dir4/file4",
+ "/dir2/file2",
+ "/file0",
+ };
+
+ private FileSystem zipFS1;
+ private FileSystem zipFS2;
+
+ @Before
+ public void setUp() throws Exception {
+ FileSystem unixFs = FileSystems.initDefaultAsNative();
+ Path testdataDir = unixFs.getPath(BlazeTestUtils.runfilesDir()).getRelative(
+ TestConstants.JAVATESTS_ROOT + "/com/google/devtools/build/lib/vfs");
+ Path zPath1 = testdataDir.getChild("sample_with_dirs.zip");
+ Path zPath2 = testdataDir.getChild("sample_without_dirs.zip");
+ zipFS1 = new ZipFileSystem(zPath1);
+ zipFS2 = new ZipFileSystem(zPath2);
+ }
+
+ private void checkExists(FileSystem fs) {
+ assertTrue(fs.getPath("/dir2/dir3/dir4").exists());
+ assertTrue(fs.getPath("/dir2/dir3/dir4/file4").exists());
+ assertFalse(fs.getPath("/dir2/dir3/dir4/bogus").exists());
+ }
+
+ @Test
+ public void testExists() {
+ checkExists(zipFS1);
+ checkExists(zipFS2);
+ }
+
+ private void checkIsFile(FileSystem fs) {
+ assertFalse(fs.getPath("/dir2/dir3/dir4").isFile());
+ assertTrue(fs.getPath("/dir2/dir3/dir4/file4").isFile());
+ assertFalse(fs.getPath("/dir2/dir3/dir4/bogus").isFile());
+ }
+
+ @Test
+ public void testIsFile() {
+ checkIsFile(zipFS1);
+ checkIsFile(zipFS2);
+ }
+
+ private void checkIsDir(FileSystem fs) {
+ assertTrue(fs.getPath("/dir2/dir3/dir4").isDirectory());
+ assertFalse(fs.getPath("/dir2/dir3/dir4/file4").isDirectory());
+ assertFalse(fs.getPath("/bogus/mobogus").isDirectory());
+ assertFalse(fs.getPath("/bogus").isDirectory());
+ }
+
+ @Test
+ public void testIsDir() {
+ checkIsDir(zipFS1);
+ checkIsDir(zipFS2);
+ }
+
+ /**
+ * Recursively add the contents of a given path, rendered as strings, into a
+ * given list.
+ */
+ private static void listChildren(Path p, List<String> list)
+ throws IOException {
+ for (Path c : p.getDirectoryEntries()) {
+ list.add(c.getPathString());
+ if (c.isDirectory()) {
+ listChildren(c, list);
+ }
+ }
+ }
+
+ private void checkListing(FileSystem fs) throws Exception {
+ List<String> list = new ArrayList<>();
+ listChildren(fs.getRootDirectory(), list);
+ Collections.sort(list);
+ assertEquals(Lists.newArrayList(LISTING), list);
+ }
+
+ @Test
+ public void testListing() throws Exception {
+ checkListing(zipFS1);
+ checkListing(zipFS2);
+
+ // Regression test for: creation of a path (i.e. a file *name*)
+ // must not affect the result of getDirectoryEntries().
+ zipFS1.getPath("/dir1/notthere");
+ checkListing(zipFS1);
+ }
+
+ private void checkFileSize(FileSystem fs, String name, long expectedSize)
+ throws IOException {
+ assertEquals(expectedSize, fs.getPath(name).getFileSize());
+ }
+
+ @Test
+ public void testCanReadRoot() {
+ Path rootDirectory = zipFS1.getRootDirectory();
+ assertTrue(rootDirectory.isDirectory());
+ }
+
+ @Test
+ public void testFileSize() throws IOException {
+ checkFileSize(zipFS1, "/dir1/file1a", 5);
+ checkFileSize(zipFS2, "/dir1/file1a", 5);
+ checkFileSize(zipFS1, "/dir2/dir3/dir4/file4", 5000);
+ checkFileSize(zipFS2, "/dir2/dir3/dir4/file4", 5000);
+ }
+
+ private void checkCantGetFileSize(FileSystem fs, String name) {
+ try {
+ fs.getPath(name).getFileSize();
+ fail();
+ } catch (IOException expected) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testCantGetFileSize() {
+ checkCantGetFileSize(zipFS1, "/dir2/dir3/dir4/bogus");
+ checkCantGetFileSize(zipFS2, "/dir2/dir3/dir4/bogus");
+ }
+
+ private void checkOpenFile(FileSystem fs, String name, int expectedSize)
+ throws Exception {
+ InputStream is = fs.getPath(name).getInputStream();
+ List<String> lines = CharStreams.readLines(new InputStreamReader(is, "ISO-8859-1"));
+ assertEquals(expectedSize, lines.size());
+ for (int i = 0; i < expectedSize; i++) {
+ assertEquals("body", lines.get(i));
+ }
+ }
+
+ @Test
+ public void testOpenSmallFile() throws Exception {
+ checkOpenFile(zipFS1, "/dir1/file1a", 1);
+ checkOpenFile(zipFS2, "/dir1/file1a", 1);
+ }
+
+ @Test
+ public void testOpenBigFile() throws Exception {
+ checkOpenFile(zipFS1, "/dir2/dir3/dir4/file4", 1000);
+ checkOpenFile(zipFS2, "/dir2/dir3/dir4/file4", 1000);
+ }
+
+ private void checkCantOpenFile(FileSystem fs, String name) {
+ try {
+ fs.getPath(name).getInputStream();
+ fail();
+ } catch (IOException expected) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testCantOpenFile() throws Exception {
+ checkCantOpenFile(zipFS1, "/dir2/dir3/dir4/bogus");
+ checkCantOpenFile(zipFS2, "/dir2/dir3/dir4/bogus");
+ }
+
+ private void checkCantCreateAnything(FileSystem fs, String name) {
+ Path p = fs.getPath(name);
+ try {
+ p.createDirectory();
+ fail();
+ } catch (Exception expected) {}
+ try {
+ FileSystemUtils.createEmptyFile(p);
+ fail();
+ } catch (Exception expected) {}
+ try {
+ p.createSymbolicLink(p);
+ fail();
+ } catch (Exception expected) {}
+ }
+
+ @Test
+ public void testCantCreateAnything() throws Exception {
+ checkCantCreateAnything(zipFS1, "/dir2/dir3/dir4/new");
+ checkCantCreateAnything(zipFS2, "/dir2/dir3/dir4/new");
+ }
+
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfoTest.java b/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfoTest.java
new file mode 100644
index 0000000000..dbdd64a328
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfoTest.java
@@ -0,0 +1,73 @@
+// Copyright 2014 Google Inc. 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.inmemoryfs;
+
+import static org.junit.Assert.fail;
+
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.util.Clock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class InMemoryContentInfoTest {
+
+ private Clock clock;
+
+ @Before
+ public void setUp() throws Exception {
+ clock = BlazeClock.instance();
+ }
+
+ @Test
+ public void testDirectoryCannotAddNullChild() {
+ InMemoryDirectoryInfo directory = new InMemoryDirectoryInfo(clock);
+
+ try {
+ directory.addChild("bar", null);
+ fail("NullPointerException not thrown.");
+ } catch (NullPointerException e) {
+ // success.
+ }
+ }
+
+ @Test
+ public void testDirectoryCannotAddChildTwice() {
+ InMemoryDirectoryInfo directory = new InMemoryDirectoryInfo(clock);
+ InMemoryFileInfo otherFile = new InMemoryFileInfo(clock);
+ directory.addChild("bar", otherFile);
+
+ try {
+ directory.addChild("bar", otherFile);
+ fail("IllegalArgumentException not thrown.");
+ } catch (IllegalArgumentException e) {
+ // success.
+ }
+ }
+
+ @Test
+ public void testDirectoryRemoveNonExistingChild() {
+ InMemoryDirectoryInfo directory = new InMemoryDirectoryInfo(clock);
+ try {
+ directory.removeChild("bar");
+ fail();
+ } catch (IllegalArgumentException e) {
+ // success
+ }
+ }
+
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java
new file mode 100644
index 0000000000..65ea6f6557
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java
@@ -0,0 +1,414 @@
+// Copyright 2014 Google Inc. 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.inmemoryfs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.testutil.TestThread;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.ScopeEscapableFileSystemTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Tests for {@link InMemoryFileSystem}. Note that most tests are inherited
+ * from {@link ScopeEscapableFileSystemTest} and ancestors. This specific
+ * file focuses only on concurrency tests.
+ *
+ */
+@RunWith(JUnit4.class)
+public class InMemoryFileSystemTest extends ScopeEscapableFileSystemTest {
+
+ @Override
+ public FileSystem getFreshFileSystem() {
+ return new InMemoryFileSystem(BlazeClock.instance(), SCOPE_ROOT);
+ }
+
+ @Override
+ public void destroyFileSystem(FileSystem fileSystem) {
+ // Nothing.
+ }
+
+ private static final int NUM_THREADS_FOR_CONCURRENCY_TESTS = 10;
+ private static final String TEST_FILE_DATA = "data";
+
+ /**
+ * Writes the given data to the given file.
+ */
+ private static void writeToFile(Path path, String data) throws IOException {
+ OutputStream out = path.getOutputStream();
+ out.write(data.getBytes(Charset.defaultCharset()));
+ out.close();
+ }
+
+ /**
+ * Tests concurrent creation of a substantial tree hierarchy including
+ * files, directories, symlinks, file contents, and permissions.
+ */
+ @Test
+ public void testConcurrentTreeConstruction() throws Exception {
+ final int NUM_TO_WRITE = 10000;
+ final AtomicInteger baseSelector = new AtomicInteger();
+
+ // 1) Define the intended path structure.
+ class PathCreator extends TestThread {
+ @Override
+ public void runTest() throws Exception {
+ Path base = testFS.getPath("/base" + baseSelector.getAndIncrement());
+ base.createDirectory();
+
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ Path subdir1 = base.getRelative("subdir1_" + i);
+ subdir1.createDirectory();
+ Path subdir2 = base.getRelative("subdir2_" + i);
+ subdir2.createDirectory();
+
+ Path file = base.getRelative("somefile" + i);
+ writeToFile(file, TEST_FILE_DATA);
+
+ subdir1.setReadable(true);
+ subdir2.setReadable(false);
+ file.setReadable(true);
+
+ subdir1.setWritable(false);
+ subdir2.setWritable(true);
+ file.setWritable(false);
+
+ subdir1.setExecutable(false);
+ subdir2.setExecutable(true);
+ file.setExecutable(false);
+
+ subdir1.setLastModifiedTime(100);
+ subdir2.setLastModifiedTime(200);
+ file.setLastModifiedTime(300);
+
+ Path symlink = base.getRelative("symlink" + i);
+ symlink.createSymbolicLink(file);
+ }
+ }
+ }
+
+ // 2) Construct the tree.
+ Collection<TestThread> threads =
+ Lists.newArrayListWithCapacity(NUM_THREADS_FOR_CONCURRENCY_TESTS);
+ for (int i = 0; i < NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ TestThread thread = new PathCreator();
+ thread.start();
+ threads.add(thread);
+ }
+ for (TestThread thread : threads) {
+ thread.joinAndAssertState(0);
+ }
+
+ // 3) Define the validation logic.
+ class PathValidator extends TestThread {
+ @Override
+ public void runTest() throws Exception {
+ Path base = testFS.getPath("/base" + baseSelector.getAndIncrement());
+ assertTrue(base.exists());
+ assertFalse(base.getRelative("notreal").exists());
+
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ Path subdir1 = base.getRelative("subdir1_" + i);
+ assertTrue(subdir1.exists());
+ assertTrue(subdir1.isDirectory());
+ assertTrue(subdir1.isReadable());
+ assertFalse(subdir1.isWritable());
+ assertFalse(subdir1.isExecutable());
+ assertEquals(100, subdir1.getLastModifiedTime());
+
+ Path subdir2 = base.getRelative("subdir2_" + i);
+ assertTrue(subdir2.exists());
+ assertTrue(subdir2.isDirectory());
+ assertFalse(subdir2.isReadable());
+ assertTrue(subdir2.isWritable());
+ assertTrue(subdir2.isExecutable());
+ assertEquals(200, subdir2.getLastModifiedTime());
+
+ Path file = base.getRelative("somefile" + i);
+ assertTrue(file.exists());
+ assertTrue(file.isFile());
+ assertTrue(file.isReadable());
+ assertFalse(file.isWritable());
+ assertFalse(file.isExecutable());
+ assertEquals(300, file.getLastModifiedTime());
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(file.getInputStream(), Charset.defaultCharset()));
+ assertEquals(TEST_FILE_DATA, reader.readLine());
+ assertEquals(null, reader.readLine());
+
+ Path symlink = base.getRelative("symlink" + i);
+ assertTrue(symlink.exists());
+ assertTrue(symlink.isSymbolicLink());
+ assertEquals(file.asFragment(), symlink.readSymbolicLink());
+ }
+ }
+ }
+
+ // 4) Validate the results.
+ baseSelector.set(0);
+ threads = Lists.newArrayListWithCapacity(NUM_THREADS_FOR_CONCURRENCY_TESTS);
+ for (int i = 0; i < NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ TestThread thread = new PathValidator();
+ thread.start();
+ threads.add(thread);
+ }
+ for (TestThread thread : threads) {
+ thread.joinAndAssertState(0);
+ }
+ }
+
+ /**
+ * Tests concurrent creation of many files, all within the same directory.
+ */
+ @Test
+ public void testConcurrentDirectoryConstruction() throws Exception {
+ final int NUM_TO_WRITE = 10000;
+ final AtomicInteger baseSelector = new AtomicInteger();
+
+ // 1) Define the intended path structure.
+ class PathCreator extends TestThread {
+ @Override
+ public void runTest() throws Exception {
+ final int threadId = baseSelector.getAndIncrement();
+ Path base = testFS.getPath("/common_dir");
+ base.createDirectory();
+
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ Path file = base.getRelative("somefile_" + threadId + "_" + i);
+ writeToFile(file, TEST_FILE_DATA);
+ file.setReadable(i % 2 == 0);
+ file.setWritable(i % 3 == 0);
+ file.setExecutable(i % 4 == 0);
+ file.setLastModifiedTime(i);
+ Path symlink = base.getRelative("symlink_" + threadId + "_" + i);
+ symlink.createSymbolicLink(file);
+ }
+ }
+ }
+
+ // 2) Create the files.
+ Collection<TestThread> threads =
+ Lists.newArrayListWithCapacity(NUM_THREADS_FOR_CONCURRENCY_TESTS);
+ for (int i = 0; i < NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ TestThread thread = new PathCreator();
+ thread.start();
+ threads.add(thread);
+ }
+ for (TestThread thread : threads) {
+ thread.joinAndAssertState(0);
+ }
+
+ // 3) Define the validation logic.
+ class PathValidator extends TestThread {
+ @Override
+ public void runTest() throws Exception {
+ final int threadId = baseSelector.getAndIncrement();
+ Path base = testFS.getPath("/common_dir");
+ assertTrue(base.exists());
+
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ Path file = base.getRelative("somefile_" + threadId + "_" + i);
+ assertTrue(file.exists());
+ assertTrue(file.isFile());
+ assertEquals(i % 2 == 0, file.isReadable());
+ assertEquals(i % 3 == 0, file.isWritable());
+ assertEquals(i % 4 == 0, file.isExecutable());
+ assertEquals(i, file.getLastModifiedTime());
+ if (file.isReadable()) {
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(file.getInputStream(), Charset.defaultCharset()));
+ assertEquals(TEST_FILE_DATA, reader.readLine());
+ assertEquals(null, reader.readLine());
+ }
+
+ Path symlink = base.getRelative("symlink_" + threadId + "_" + i);
+ assertTrue(symlink.exists());
+ assertTrue(symlink.isSymbolicLink());
+ assertEquals(file.asFragment(), symlink.readSymbolicLink());
+ }
+ }
+ }
+
+ // 4) Validate the results.
+ baseSelector.set(0);
+ threads = Lists.newArrayListWithCapacity(NUM_THREADS_FOR_CONCURRENCY_TESTS);
+ for (int i = 0; i < NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ TestThread thread = new PathValidator();
+ thread.start();
+ threads.add(thread);
+ }
+ for (TestThread thread : threads) {
+ thread.joinAndAssertState(0);
+ }
+ }
+
+ /**
+ * Tests concurrent file deletion.
+ */
+ @Test
+ public void testConcurrentDeletion() throws Exception {
+ final int NUM_TO_WRITE = 10000;
+ final AtomicInteger baseSelector = new AtomicInteger();
+
+ final Path base = testFS.getPath("/base");
+ base.createDirectory();
+
+ // 1) Create a bunch of files.
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ writeToFile(base.getRelative("file" + i), TEST_FILE_DATA);
+ }
+
+ // 2) Define our deletion strategy.
+ class FileDeleter extends TestThread {
+ @Override
+ public void runTest() throws Exception {
+ for (int i = 0; i < NUM_TO_WRITE / NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ int whichFile = baseSelector.getAndIncrement();
+ Path file = base.getRelative("file" + whichFile);
+ if (whichFile % 25 != 0) {
+ assertTrue(file.delete());
+ } else {
+ // Throw another concurrent access point into the mix.
+ file.setExecutable(whichFile % 2 == 0);
+ }
+ assertFalse(base.getRelative("doesnotexist" + whichFile).delete());
+ }
+ }
+ }
+
+ // 3) Delete some files.
+ Collection<TestThread> threads =
+ Lists.newArrayListWithCapacity(NUM_THREADS_FOR_CONCURRENCY_TESTS);
+ for (int i = 0; i < NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ TestThread thread = new FileDeleter();
+ thread.start();
+ threads.add(thread);
+ }
+ for (TestThread thread : threads) {
+ thread.joinAndAssertState(0);
+ }
+
+ // 4) Check the results.
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ Path file = base.getRelative("file" + i);
+ if (i % 25 != 0) {
+ assertFalse(file.exists());
+ } else {
+ assertTrue(file.exists());
+ assertEquals(i % 2 == 0, file.isExecutable());
+ }
+ }
+ }
+
+ /**
+ * Tests concurrent file renaming.
+ */
+ @Test
+ public void testConcurrentRenaming() throws Exception {
+ final int NUM_TO_WRITE = 10000;
+ final AtomicInteger baseSelector = new AtomicInteger();
+
+ final Path base = testFS.getPath("/base");
+ base.createDirectory();
+
+ // 1) Create a bunch of files.
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ writeToFile(base.getRelative("file" + i), TEST_FILE_DATA);
+ }
+
+ // 2) Define our renaming strategy.
+ class FileDeleter extends TestThread {
+ @Override
+ public void runTest() throws Exception {
+ for (int i = 0; i < NUM_TO_WRITE / NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ int whichFile = baseSelector.getAndIncrement();
+ Path file = base.getRelative("file" + whichFile);
+ if (whichFile % 25 != 0) {
+ Path newName = base.getRelative("newname" + whichFile);
+ file.renameTo(newName);
+ } else {
+ // Throw another concurrent access point into the mix.
+ file.setExecutable(whichFile % 2 == 0);
+ }
+ assertFalse(base.getRelative("doesnotexist" + whichFile).delete());
+ }
+ }
+ }
+
+ // 3) Rename some files.
+ Collection<TestThread> threads =
+ Lists.newArrayListWithCapacity(NUM_THREADS_FOR_CONCURRENCY_TESTS);
+ for (int i = 0; i < NUM_THREADS_FOR_CONCURRENCY_TESTS; i++) {
+ TestThread thread = new FileDeleter();
+ thread.start();
+ threads.add(thread);
+ }
+ for (TestThread thread : threads) {
+ thread.joinAndAssertState(0);
+ }
+
+ // 4) Check the results.
+ for (int i = 0; i < NUM_TO_WRITE; i++) {
+ Path file = base.getRelative("file" + i);
+ if (i % 25 != 0) {
+ assertFalse(file.exists());
+ assertTrue(base.getRelative("newname" + i).exists());
+ } else {
+ assertTrue(file.exists());
+ assertEquals(i % 2 == 0, file.isExecutable());
+ }
+ }
+ }
+
+ @Test
+ public void testEloop() throws Exception {
+ Path a = testFS.getPath("/a");
+ Path b = testFS.getPath("/b");
+ a.createSymbolicLink(new PathFragment("b"));
+ b.createSymbolicLink(new PathFragment("a"));
+ try {
+ a.stat();
+ } catch (IOException e) {
+ assertEquals("/a (Too many levels of symbolic links)", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testEloopSelf() throws Exception {
+ Path a = testFS.getPath("/a");
+ a.createSymbolicLink(new PathFragment("a"));
+ try {
+ a.stat();
+ } catch (IOException e) {
+ assertEquals("/a (Too many levels of symbolic links)", e.getMessage());
+ }
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/sample_with_dirs.zip b/src/test/java/com/google/devtools/build/lib/vfs/sample_with_dirs.zip
new file mode 100644
index 0000000000..22ff63cd32
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/sample_with_dirs.zip
Binary files differ
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/sample_without_dirs.zip b/src/test/java/com/google/devtools/build/lib/vfs/sample_without_dirs.zip
new file mode 100644
index 0000000000..f3ec5ab792
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/sample_without_dirs.zip
Binary files differ
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/util/FileSystems.java b/src/test/java/com/google/devtools/build/lib/vfs/util/FileSystems.java
new file mode 100644
index 0000000000..6a79a2b948
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/util/FileSystems.java
@@ -0,0 +1,93 @@
+// Copyright 2014 Google Inc. 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.util;
+
+import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.JavaIoFileSystem;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.UnionFileSystem;
+import com.google.devtools.build.lib.vfs.UnixFileSystem;
+import com.google.devtools.build.lib.vfs.ZipFileSystem;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * This static file system singleton manages access to a single default
+ * {@link FileSystem} instance created within the methods of this class.
+ */
+@ThreadSafe
+public final class FileSystems {
+
+ private FileSystems() {}
+
+ private static FileSystem defaultFileSystem;
+
+ /**
+ * Initializes the default {@link FileSystem} instance as a platform native
+ * (Unix) file system, creating one iff needed, and returns the instance.
+ *
+ * <p>This method is idempotent as long as the initialization is of the same
+ * type (Native/JavaIo/Union).
+ */
+ public static synchronized FileSystem initDefaultAsNative() {
+ if (!(defaultFileSystem instanceof UnixFileSystem)) {
+ defaultFileSystem = new UnixFileSystem();
+ }
+ return defaultFileSystem;
+ }
+
+ /**
+ * Initializes the default {@link FileSystem} instance as a java.io.File
+ * file system, creating one iff needed, and returns the instance.
+ *
+ * <p>This method is idempotent as long as the initialization is of the same
+ * type (Native/JavaIo/Union).
+ */
+ public static synchronized FileSystem initDefaultAsJavaIo() {
+ if (!(defaultFileSystem instanceof JavaIoFileSystem)) {
+ defaultFileSystem = new JavaIoFileSystem();
+ }
+ return defaultFileSystem;
+ }
+
+ /**
+ * Initializes the default {@link FileSystem} instance as a
+ * {@link UnionFileSystem}, creating one iff needed,
+ * and returns the instance.
+ *
+ * <p>This method is idempotent as long as the initialization is of the same
+ * type (Native/JavaIo/Union).
+ *
+ * @param prefixMapping the desired mapping of path prefixes to delegate file systems
+ * @param rootFileSystem the default file system for paths that don't match any prefix map
+ */
+ public static synchronized FileSystem initDefaultAsUnion(
+ Map<PathFragment, FileSystem> prefixMapping, FileSystem rootFileSystem) {
+ if (!(defaultFileSystem instanceof UnionFileSystem)) {
+ defaultFileSystem = new UnionFileSystem(prefixMapping, rootFileSystem);
+ }
+ return defaultFileSystem;
+ }
+
+ /**
+ * Returns a new instance of a simple {@link FileSystem} implementation that
+ * presents the contents of a zip file as a read-only file system view.
+ */
+ public static FileSystem newZipFileSystem(Path zipFile) throws IOException {
+ return new ZipFileSystem(zipFile);
+ }
+}
diff --git a/src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java b/src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java
new file mode 100644
index 0000000000..5d93351a8d
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/lib/vfs/util/FsApparatus.java
@@ -0,0 +1,158 @@
+// Copyright 2014 Google Inc. 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.util;
+
+import com.google.devtools.build.lib.testutil.TestUtils;
+import com.google.devtools.build.lib.util.BlazeClock;
+import com.google.devtools.build.lib.util.StringUtilities;
+import com.google.devtools.build.lib.vfs.FileSystem;
+import com.google.devtools.build.lib.vfs.FileSystemUtils;
+import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.inmemoryfs.InMemoryFileSystem;
+
+import junit.framework.AssertionFailedError;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Base class for a testing apparatus for a scratch filesystem.
+ */
+public class FsApparatus {
+
+ /* ---------- State that the apparatus initializes / operates on --------- */
+ protected FileSystem fileSystem = null;
+ protected Path workingDir = null;
+
+ public static FsApparatus newInMemory() {
+ return new FsApparatus();
+ }
+
+ // TestUtil.getTmpDir is slow, so cache the result here
+ private static final String TMP_DIR =
+ new File(TestUtils.tmpDir(), "bs").toString();
+
+
+ /**
+ * When using a Native file system, absolute paths will be treated as absolute paths on the unix
+ * file system, as opposed to paths relative to the backing temp directory. So for simplicity,
+ * you ought to only use relative paths for FsApparatus#file, FsApparatus#dir, and
+ * FsApparatus#path. Otherwise, be aware of the following issue
+ *
+ * Path p1 = scratch.path(...);
+ * Path p2 = scratch.path(p1.getPathString());
+ *
+ * We'd like the invariant that p1.equals(p2) regardless if scratch is in-memory or not, but this
+ * does not hold with our usage of Unix filesystems.
+ */
+ public static FsApparatus newNative() {
+ FileSystem fs = FileSystems.initDefaultAsNative();
+ Path wd = fs.getPath(TMP_DIR);
+
+ try {
+ FileSystemUtils.deleteTree(wd);
+ } catch (IOException e) {
+ throw new AssertionFailedError(e.getMessage());
+ }
+
+ return new FsApparatus(fs, wd);
+ }
+
+ private FsApparatus() {
+ fileSystem = new InMemoryFileSystem(BlazeClock.instance());
+ workingDir = fileSystem.getPath("/");
+ }
+
+ public FsApparatus(FileSystem fs, Path cwd) {
+ fileSystem = fs;
+ workingDir = cwd;
+ }
+
+ public FsApparatus(FileSystem fs) {
+ fileSystem = fs;
+ workingDir = fs.getPath("/");
+ }
+
+ public FileSystem fs() {
+ return fileSystem;
+ }
+
+ /**
+ * Initializes this apparatus (if it hasn't been initialized yet), and creates
+ * a scratch file in the scratch filesystem with the given {@code pathName}
+ * with {@code lines} being its content. The method returns a Path instance
+ * for the scratch file.
+ */
+ public Path file(String pathName, String... lines) throws IOException {
+ Path file = path(pathName);
+ Path parentDir = file.getParentDirectory();
+ if (!parentDir.exists()) {
+ FileSystemUtils.createDirectoryAndParents(parentDir);
+ }
+ if (file.exists()) {
+ throw new IOException("Could not create scratch file (file exists) "
+ + file);
+ }
+ String fileContent = StringUtilities.joinLines(lines);
+ FileSystemUtils.writeContentAsLatin1(file, fileContent);
+ return file;
+ }
+
+ /**
+ * Initializes this apparatus (if it hasn't been initialized yet), and creates
+ * a directory in the scratch filesystem, with the given {@code pathName}.
+ * Creates parent directories as necessary.
+ */
+ public Path dir(String pathName) throws IOException {
+ Path dir = path(pathName);
+ if (!dir.exists()) {
+ FileSystemUtils.createDirectoryAndParents(dir);
+ }
+ if (!dir.isDirectory()) {
+ throw new IOException("Exists, but is not a directory: " + dir);
+ }
+ return dir;
+ }
+
+ /**
+ * Initializes this apparatus (if it hasn't been initialized yet), and returns
+ * a path object describing a file, directory, or symlink pointed at by
+ * {@code pathName}. Note that this will not create any entity in the
+ * filesystem; i.e., the file that the object is describing may not exist in
+ * the filesystem.
+ */
+ public Path path(String pathName) {
+ return workingDir.getRelative(pathName);
+ }
+
+ /**
+ * Create a fresh directory in the system temporary directory, instead of the
+ * testing directory provided by the testing framework. This path is usually
+ * shorter than a path starting with TestUtil.getTmpDir(). We care about the
+ * length because of the path length restriction for Unix local socket files.
+ *
+ * Clients are responsible for deleting the directory after tests.
+ */
+ public Path createUnixTempDir() throws IOException {
+ if (fileSystem instanceof InMemoryFileSystem) {
+ throw new IOException("Can not create Unix temporary directories in "
+ + "an in-memory file system");
+ }
+ File file = File.createTempFile("scratch", "tmp");
+ final Path path = fileSystem.getPath(file.getAbsolutePath());
+ path.delete();
+ path.createDirectory();
+ return path;
+ }
+}