// Copyright 2014 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.lib.vfs; import static com.google.common.truth.Truth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; 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.io.BaseEncoding; import com.google.devtools.build.lib.testutil.TestUtils; import com.google.devtools.build.lib.unix.NativePosixFiles; import com.google.devtools.build.lib.util.Fingerprint; import com.google.devtools.build.lib.util.Preconditions; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.FileAlreadyExistsException; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.After; import org.junit.Before; import org.junit.Test; /** * 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 boolean supportsHardlinks; protected Path workingDir; // Some useful examples of various kinds of files (mnemonic: "x" = "eXample") protected Path xNothing; protected Path xLink; protected Path xFile; protected Path xNonEmptyDirectory; protected Path xNonEmptyDirectoryFoo; protected Path xEmptyDirectory; @Before public final void createDirectories() throws Exception { executeBeforeCreatingDirectories(); testFS = getFreshFileSystem(); workingDir = testFS.getPath(getTestTmpDir()); cleanUpWorkingDirectory(workingDir); supportsSymlinks = testFS.supportsSymbolicLinksNatively(); supportsHardlinks = testFS.supportsHardLinksNatively(); // % ls -lR // -rw-rw-r-- xFile // drwxrwxr-x xNonEmptyDirectory // -rw-rw-r-- xNonEmptyDirectory/foo // drwxrwxr-x xEmptyDirectory xNothing = absolutize("xNothing"); xLink = absolutize("xLink"); xFile = absolutize("xFile"); xNonEmptyDirectory = absolutize("xNonEmptyDirectory"); xNonEmptyDirectoryFoo = xNonEmptyDirectory.getChild("foo"); xEmptyDirectory = absolutize("xEmptyDirectory"); FileSystemUtils.createEmptyFile(xFile); xNonEmptyDirectory.createDirectory(); FileSystemUtils.createEmptyFile(xNonEmptyDirectoryFoo); xEmptyDirectory.createDirectory(); } protected void executeBeforeCreatingDirectories() throws Exception { // This method exists because LazyDigestFileSystemTest requires some code to be run before // createDirectories(). } @After public final void destroyFileSystem() 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 NativePosixFiles.isSymbolicLink(file); } protected void setWritable(File file) throws IOException { NativePosixFiles.setWritable(file); } protected void setExecutable(File file) throws IOException { NativePosixFiles.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); } 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(PathFragment.create("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(0, newPath.getDirectoryEntries().size()); } @Test public void testCreateDirectoryIsOnlyChildInParent() throws Exception { Path newPath = xEmptyDirectory.getChild("new-dir"); newPath.createDirectory(); assertThat(newPath.getParentDirectory().getDirectoryEntries()).hasSize(1); 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(0, newPath.getDirectoryEntries().size()); } @Test public void testCreateDirectoriesIsOnlyChildInParent() throws Exception { Path newPath = absolutize("new-dir/sub/directory"); FileSystemUtils.createDirectoryAndParents(newPath); assertThat(newPath.getParentDirectory().getDirectoryEntries()).hasSize(1); assertThat(newPath.getParentDirectory().getDirectoryEntries()).containsExactly(newPath); } @Test public void testCreateEmptyFileIsEmpty() throws Exception { Path newPath = xEmptyDirectory.getChild("new-file"); FileSystemUtils.createEmptyFile(newPath); assertEquals(0, newPath.getFileSize()); } @Test public void testCreateFileIsOnlyChildInParent() throws Exception { Path newPath = xEmptyDirectory.getChild("new-file"); FileSystemUtils.createEmptyFile(newPath); assertThat(newPath.getParentDirectory().getDirectoryEntries()).hasSize(1); 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) { assertThat(e).hasMessage(xFile + " (File exists)"); } } @Test public void testCannotCreateDirectoryWithoutExistingParent() throws Exception { Path newPath = testFS.getPath("/deep/new-dir"); try { newPath.createDirectory(); fail(); } catch (FileNotFoundException e) { assertThat(e.getMessage()).endsWith(" (No such file or directory)"); } } @Test public void testCannotCreateDirectoryWithReadOnlyParent() throws Exception { xEmptyDirectory.setWritable(false); Path xChildOfReadonlyDir = xEmptyDirectory.getChild("x"); try { xChildOfReadonlyDir.createDirectory(); fail(); } catch (IOException e) { assertThat(e).hasMessage(xChildOfReadonlyDir + " (Permission denied)"); } } @Test public void testCannotCreateFileWithoutExistingParent() throws Exception { Path newPath = testFS.getPath("/non-existing-dir/new-file"); try { FileSystemUtils.createEmptyFile(newPath); fail(); } catch (FileNotFoundException e) { assertThat(e.getMessage()).endsWith(" (No such file or directory)"); } } @Test public void testCannotCreateFileWithReadOnlyParent() throws Exception { xEmptyDirectory.setWritable(false); Path xChildOfReadonlyDir = xEmptyDirectory.getChild("x"); try { FileSystemUtils.createEmptyFile(xChildOfReadonlyDir); fail(); } catch (IOException e) { assertThat(e).hasMessage(xChildOfReadonlyDir + " (Permission denied)"); } } @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) { assertThat(e.getMessage()).endsWith(" (Not a directory)"); } } @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) { assertThat(e.getMessage()).endsWith(" (Not a directory)"); } } // 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."); } assertThat(ex).hasMessage(xFile + " (Not a directory)"); } } @Test public void testGetDirectoryEntriesThrowsExceptionForNonexistingPath() { Path somePath = testFS.getPath("/non-existing-path"); try { somePath.getDirectoryEntries(); fail("FileNotFoundException not thrown."); } catch (Exception x) { assertThat(x).hasMessage(somePath + " (No such file or directory)"); } } // 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) { assertThat(e).hasMessage(xNonEmptyDirectory + " (Directory not empty)"); } } @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) { assertThat(x).hasMessage(newPath + " (No such file or directory)"); } } // 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) { assertThat(e).hasMessage(newPath + " (No such file or directory)"); } } @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 { try (OutputStream outStream = xFile.getOutputStream()) { outStream.write(1); } try (InputStream inStream = xFile.getInputStream()) { inStream.read(); assertEquals(-1, inStream.read()); } } @Test public void testInputAndOutputStream() throws Exception { try (OutputStream outStream = xFile.getOutputStream()) { for (int i = 33; i < 126; i++) { outStream.write(i); } } try (InputStream inStream = xFile.getInputStream()) { for (int i = 33; i < 126; i++) { int readValue = inStream.read(); assertEquals(i, readValue); } } } @Test public void testInputAndOutputStreamAppend() throws Exception { try (OutputStream outStream = xFile.getOutputStream()) { for (int i = 33; i < 126; i++) { outStream.write(i); } } try (OutputStream appendOut = xFile.getOutputStream(true)) { for (int i = 126; i < 155; i++) { appendOut.write(i); } } try (InputStream inStream = xFile.getInputStream()) { for (int i = 33; i < 155; i++) { int readValue = inStream.read(); assertEquals(i, readValue); } } } @Test public void testInputAndOutputStreamNoAppend() throws Exception { try (OutputStream outStream = xFile.getOutputStream()) { outStream.write(1); } try (OutputStream noAppendOut = xFile.getOutputStream(false)) { } try (InputStream inStream = xFile.getInputStream()) { assertEquals(-1, inStream.read()); } } @Test public void testGetOutputStreamCreatesFile() throws Exception { Path newFile = absolutize("does_not_exist_yet.txt"); try (OutputStream out = newFile.getOutputStream()) { out.write(42); } assertTrue(newFile.isFile()); } @Test public void testOutputStreamThrowExceptionOnDirectory() throws Exception { try { xEmptyDirectory.getOutputStream(); fail("The Exception was not thrown!"); } catch (IOException ex) { assertThat(ex).hasMessage(xEmptyDirectory + " (Is a directory)"); } } @Test public void testInputStreamThrowExceptionOnDirectory() throws Exception { try { xEmptyDirectory.getInputStream(); fail("The Exception was not thrown!"); } catch (IOException ex) { assertThat(ex).hasMessage(xEmptyDirectory + " (Is a directory)"); } } // 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()); assertThat(xEmptyDirectory.getDirectoryEntries()).isNotEmpty(); } @Test public void testCantRenameDirToExistingNonEmptyDir() throws Exception { try { xEmptyDirectory.renameTo(xNonEmptyDirectory); fail(); } catch (IOException e) { assertThat(e.getMessage()).endsWith(" (Directory not empty)"); } } @Test public void testCantRenameDirToExistingNonEmptyDirNothingChanged() throws Exception { try { xEmptyDirectory.renameTo(xNonEmptyDirectory); fail(); } catch (IOException e) { // Expected } assertTrue(xNonEmptyDirectory.isDirectory()); assertTrue(xEmptyDirectory.isDirectory()); assertThat(xEmptyDirectory.getDirectoryEntries()).isEmpty(); assertThat(xNonEmptyDirectory.getDirectoryEntries()).isNotEmpty(); } @Test public void testCantRenameDirToExistingFile() { try { xEmptyDirectory.renameTo(xFile); fail(); } catch (IOException e) { assertThat(e).hasMessage(xEmptyDirectory + " -> " + xFile + " (Not a directory)"); } } @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) { assertThat(e).hasMessage(xFile + " -> " + xEmptyDirectory + " (Is a directory)"); } } @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) { assertThat(e.getMessage()).endsWith(" (No such file or directory)"); } } // Test the Paths @Test public void testGetPathOnlyAcceptsAbsolutePath() { try { testFS.getPath("not-absolute"); fail("The expected Exception was not thrown."); } catch (IllegalArgumentException ex) { assertThat(ex).hasMessage("not-absolute (not an absolute path)"); } } @Test public void testGetPathOnlyAcceptsAbsolutePathFragment() { try { testFS.getPath(PathFragment.create("not-absolute")); fail("The expected Exception was not thrown."); } catch (IllegalArgumentException ex) { assertThat(ex).hasMessage("not-absolute (not an absolute path)"); } } // 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) { assertThat(ex).hasMessage(xNothing + " (No such file or directory)"); } } @Test public void testCannotSetExecutableOnNonexistingFile() throws Exception { try { xNothing.setExecutable(true); fail("No exception thrown."); } catch (FileNotFoundException ex) { assertThat(ex).hasMessage(xNothing + " (No such file or directory)"); } } @Test public void testCannotGetWritableOnNonexistingFile() throws Exception { try { xNothing.isWritable(); fail("No exception thrown."); } catch (FileNotFoundException ex) { assertThat(ex).hasMessage(xNothing + " (No such file or directory)"); } } @Test public void testCannotSetWritableOnNonexistingFile() throws Exception { try { xNothing.setWritable(false); fail("No exception thrown."); } catch (FileNotFoundException ex) { assertThat(ex).hasMessage(xNothing + " (No such file or directory)"); } } @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) { assertThat(e.getMessage()).endsWith(" (Permission denied)"); } } @Test public void testWritingToReadOnlyFileThrowsException() throws Exception { xFile.setWritable(false); try { FileSystemUtils.writeContent(xFile, "hello, world!".getBytes(UTF_8)); fail("No exception thrown."); } catch (IOException e) { assertThat(e).hasMessage(xFile + " (Permission denied)"); } } @Test public void testReadingFromUnreadableFileThrowsException() throws Exception { FileSystemUtils.writeContent(xFile, "hello, world!".getBytes(UTF_8)); xFile.setReadable(false); try { FileSystemUtils.readContent(xFile); fail("No exception thrown."); } catch (IOException e) { assertThat(e).hasMessage(xFile + " (Permission denied)"); } } @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) { assertThat(e).hasMessage(xNonEmptyDirectoryBar + " (Permission denied)"); } } @Test public void testCannotCreateDirectoryInReadOnlyDirectory() throws Exception { Path xNonEmptyDirectoryBar = xNonEmptyDirectory.getChild("bar"); xNonEmptyDirectory.setWritable(false); try { xNonEmptyDirectoryBar.createDirectory(); fail("No exception thrown."); } catch (IOException e) { assertThat(e).hasMessage(xNonEmptyDirectoryBar + " (Permission denied)"); } } @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) { assertThat(e.getMessage()).endsWith(" (Permission denied)"); } } @Test public void testCannotMoveFromReadOnlyDirectory() throws Exception { xNonEmptyDirectory.setWritable(false); try { xNonEmptyDirectoryFoo.renameTo(xNothing); fail("No exception thrown."); } catch (IOException e) { assertThat(e.getMessage()).endsWith(" (Permission denied)"); } } @Test public void testCannotDeleteInReadOnlyDirectory() throws Exception { xNonEmptyDirectory.setWritable(false); try { xNonEmptyDirectoryFoo.delete(); fail("No exception thrown."); } catch (IOException e) { assertThat(e).hasMessage(xNonEmptyDirectoryFoo + " (Permission denied)"); } } @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) { assertThat(e).hasMessage(xNonEmptyDirectoryBar + " (Permission denied)"); } } } @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(xLink, xFile); FileSystemUtils.createEmptyFile(xFile); assertEquals(xFile.asFragment(), testFS.resolveOneLink(xLink)); assertEquals(xFile, xLink.resolveSymbolicLinks()); } } @Test public void testResolveDanglingSymlinks() throws Exception { if (supportsSymlinks) { createSymbolicLink(xLink, xNothing); assertEquals(xNothing.asFragment(), testFS.resolveOneLink(xLink)); try { xLink.resolveSymbolicLinks(); fail(); } catch (IOException expected) { } } } @Test public void testResolveNonSymlinks() throws Exception { if (supportsSymlinks) { assertNull(testFS.resolveOneLink(xFile)); assertEquals(xFile, xFile.resolveSymbolicLinks()); } } @Test public void testCreateHardLink_Success() throws Exception { if (!supportsHardlinks) { return; } xFile.createHardLink(xLink); assertTrue(xFile.exists()); assertTrue(xLink.exists()); assertTrue(xFile.isFile()); assertTrue(xLink.isFile()); assertTrue(isHardLinked(xFile, xLink)); } @Test public void testCreateHardLink_NeitherOriginalNorLinkExists() throws Exception { if (!supportsHardlinks) { return; } /* Neither original file nor link file exists */ xFile.delete(); try { xFile.createHardLink(xLink); fail("expected FileNotFoundException: File \"xFile\" linked from \"xLink\" does not exist"); } catch (FileNotFoundException expected) { assertThat(expected).hasMessage("File \"xFile\" linked from \"xLink\" does not exist"); } assertFalse(xFile.exists()); assertFalse(xLink.exists()); } @Test public void testCreateHardLink_OriginalDoesNotExistAndLinkExists() throws Exception { if (!supportsHardlinks) { return; } /* link file exists and original file does not exist */ xFile.delete(); FileSystemUtils.createEmptyFile(xLink); try { xFile.createHardLink(xLink); fail("expected FileNotFoundException: File \"xFile\" linked from \"xLink\" does not exist"); } catch (FileNotFoundException expected) { assertThat(expected).hasMessage("File \"xFile\" linked from \"xLink\" does not exist"); } assertFalse(xFile.exists()); assertTrue(xLink.exists()); } @Test public void testCreateHardLink_BothOriginalAndLinkExist() throws Exception { if (!supportsHardlinks) { return; } /* Both original file and link file exist */ FileSystemUtils.createEmptyFile(xLink); try { xFile.createHardLink(xLink); fail("expected FileAlreadyExistsException: New link file \"xLink\" already exists"); } catch (FileAlreadyExistsException expected) { assertThat(expected).hasMessage("New link file \"xLink\" already exists"); } assertTrue(xFile.exists()); assertTrue(xLink.exists()); assertFalse(isHardLinked(xFile, xLink)); } protected boolean isHardLinked(Path a, Path b) throws IOException { return testFS.stat(a, false).getNodeId() == testFS.stat(b, false).getNodeId(); } }