blob: 278a288dda25812c8fa0045fc7ec4f28b0529fae [file] [log] [blame]
// 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 com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
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.TestUtils;
import com.google.devtools.build.lib.unix.NativePosixFiles;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.FileSystem.HashFunction;
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 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);
// % 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 {
assertThat(path.statIfFound()).isNull();
} 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");
assertThat(nonExistingPath.isFile()).isFalse();
}
@Test
public void testIsDirectoryForNonexistingPath() {
Path nonExistingPath = testFS.getPath("/something/strange");
assertThat(nonExistingPath.isDirectory()).isFalse();
}
@Test
public void testIsLinkForNonexistingPath() {
Path nonExistingPath = testFS.getPath("/something/strange");
assertThat(nonExistingPath.isSymbolicLink()).isFalse();
}
@Test
public void testExistsForNonexistingPath() throws Exception {
Path nonExistingPath = testFS.getPath("/something/strange");
assertThat(nonExistingPath.exists()).isFalse();
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);
assertThat(child.exists()).isFalse();
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);
assertThat(nonDir.getRelative("file").statIfFound()).isNull();
}
// 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"));
assertThat(relativeCreatedPath).isEqualTo(expectedResult);
}
// The following tests check the handling of the root directory
@Test
public void testRootIsDirectory() {
Path rootPath = testFS.getPath("/");
assertThat(rootPath.isDirectory()).isTrue();
}
@Test
public void testRootHasNoParent() {
Path rootPath = testFS.getPath("/");
assertThat(rootPath.getParentDirectory()).isNull();
}
// The following functions test the creation of files/links/directories.
@Test
public void testFileExists() throws Exception {
Path someFile = absolutize("some-file");
FileSystemUtils.createEmptyFile(someFile);
assertThat(someFile.exists()).isTrue();
assertThat(someFile.statIfFound()).isNotNull();
}
@Test
public void testFileIsFile() throws Exception {
Path someFile = absolutize("some-file");
FileSystemUtils.createEmptyFile(someFile);
assertThat(someFile.isFile()).isTrue();
}
@Test
public void testFileIsNotDirectory() throws Exception {
Path someFile = absolutize("some-file");
FileSystemUtils.createEmptyFile(someFile);
assertThat(someFile.isDirectory()).isFalse();
}
@Test
public void testFileIsNotSymbolicLink() throws Exception {
Path someFile = absolutize("some-file");
FileSystemUtils.createEmptyFile(someFile);
assertThat(someFile.isSymbolicLink()).isFalse();
}
@Test
public void testDirectoryExists() throws Exception {
Path someDirectory = absolutize("some-dir");
someDirectory.createDirectory();
assertThat(someDirectory.exists()).isTrue();
assertThat(someDirectory.statIfFound()).isNotNull();
}
@Test
public void testDirectoryIsDirectory() throws Exception {
Path someDirectory = absolutize("some-dir");
someDirectory.createDirectory();
assertThat(someDirectory.isDirectory()).isTrue();
}
@Test
public void testDirectoryIsNotFile() throws Exception {
Path someDirectory = absolutize("some-dir");
someDirectory.createDirectory();
assertThat(someDirectory.isFile()).isFalse();
}
@Test
public void testDirectoryIsNotSymbolicLink() throws Exception {
Path someDirectory = absolutize("some-dir");
someDirectory.createDirectory();
assertThat(someDirectory.isSymbolicLink()).isFalse();
}
@Test
public void testSymbolicFileLinkExists() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xFile);
assertThat(someLink.exists()).isTrue();
assertThat(someLink.statIfFound()).isNotNull();
}
}
@Test
public void testSymbolicFileLinkIsSymbolicLink() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xFile);
assertThat(someLink.isSymbolicLink()).isTrue();
}
}
@Test
public void testSymbolicFileLinkIsFile() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xFile);
assertThat(someLink.isFile()).isTrue();
}
}
@Test
public void testSymbolicFileLinkIsNotDirectory() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xFile);
assertThat(someLink.isDirectory()).isFalse();
}
}
@Test
public void testSymbolicDirLinkExists() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xEmptyDirectory);
assertThat(someLink.exists()).isTrue();
assertThat(someLink.statIfFound()).isNotNull();
}
}
@Test
public void testSymbolicDirLinkIsSymbolicLink() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xEmptyDirectory);
assertThat(someLink.isSymbolicLink()).isTrue();
}
}
@Test
public void testSymbolicDirLinkIsDirectory() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xEmptyDirectory);
assertThat(someLink.isDirectory()).isTrue();
}
}
@Test
public void testSymbolicDirLinkIsNotFile() throws Exception {
Path someLink = absolutize("some-link");
if (testFS.supportsSymbolicLinksNatively(someLink)) {
someLink.createSymbolicLink(xEmptyDirectory);
assertThat(someLink.isFile()).isFalse();
}
}
@Test
public void testChildOfNonDirectory() throws Exception {
Path somePath = absolutize("file-name");
FileSystemUtils.createEmptyFile(somePath);
Path childOfNonDir = somePath.getChild("child");
assertThat(childOfNonDir.exists()).isFalse();
expectNotFound(childOfNonDir);
}
@Test
public void testCreateDirectoryIsEmpty() throws Exception {
Path newPath = xEmptyDirectory.getChild("new-dir");
newPath.createDirectory();
assertThat(newPath.getDirectoryEntries()).isEmpty();
}
@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");
assertThat(FileSystemUtils.createDirectoryAndParents(newPath)).isTrue();
}
@Test
public void testCreateDirectoriesIsDirectory() throws Exception {
Path newPath = absolutize("new-dir/sub/directory");
FileSystemUtils.createDirectoryAndParents(newPath);
assertThat(newPath.isDirectory()).isTrue();
}
@Test
public void testCreateDirectoriesIsNotFile() throws Exception {
Path newPath = absolutize("new-dir/sub/directory");
FileSystemUtils.createDirectoryAndParents(newPath);
assertThat(newPath.isFile()).isFalse();
}
@Test
public void testCreateDirectoriesIsNotSymbolicLink() throws Exception {
Path newPath = absolutize("new-dir/sub/directory");
FileSystemUtils.createDirectoryAndParents(newPath);
assertThat(newPath.isSymbolicLink()).isFalse();
}
@Test
public void testCreateDirectoriesIsEmpty() throws Exception {
Path newPath = absolutize("new-dir/sub/directory");
FileSystemUtils.createDirectoryAndParents(newPath);
assertThat(newPath.getDirectoryEntries()).isEmpty();
}
@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);
assertThat(newPath.getFileSize()).isEqualTo(0);
}
@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 {
assertThat(xEmptyDirectory.createDirectory()).isFalse();
}
@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).hasMessageThat().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).hasMessageThat().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).hasMessageThat().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).hasMessageThat().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 {
assertThat(xEmptyDirectory.delete()).isTrue();
}
@Test
public void testDeleteDirectoryIsNotDirectory() throws Exception {
xEmptyDirectory.delete();
assertThat(xEmptyDirectory.isDirectory()).isFalse();
}
@Test
public void testDeleteDirectoryParentSize() throws Exception {
int parentSize = workingDir.getDirectoryEntries().size();
xEmptyDirectory.delete();
assertThat(parentSize - 1).isEqualTo(workingDir.getDirectoryEntries().size());
}
@Test
public void testDeleteFile() throws Exception {
assertThat(xFile.delete()).isTrue();
}
@Test
public void testDeleteFileIsNotFile() throws Exception {
xFile.delete();
assertThat(xEmptyDirectory.isFile()).isFalse();
}
@Test
public void testDeleteFileParentSize() throws Exception {
int parentSize = workingDir.getDirectoryEntries().size();
xFile.delete();
assertThat(parentSize - 1).isEqualTo(workingDir.getDirectoryEntries().size());
}
@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);
assertThat(newPath2.delete()).isTrue();
assertThat(xEmptyDirectory.getDirectoryEntries()).containsExactly(newPath1, newPath3);
}
@Test
public void testDeleteNonExistingDir() throws Exception {
Path path = xEmptyDirectory.getRelative("non-existing-dir");
assertThat(path.delete()).isFalse();
}
@Test
public void testDeleteNotADirectoryPath() throws Exception {
Path path = xFile.getChild("new-file");
assertThat(path.delete()).isFalse();
}
// 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
}
assertThat(xNonEmptyDirectory.isDirectory()).isTrue();
}
@Test
public void testDeleteNonEmptyDirectoryNotDeletedFile() throws Exception {
try {
xNonEmptyDirectory.delete();
fail();
} catch (IOException e) {
// Expected
}
assertThat(xNonEmptyDirectoryFoo.isFile()).isTrue();
}
@Test
public void testCannotRemoveRoot() {
Path rootDirectory = testFS.getRootDirectory();
try {
rootDirectory.delete();
fail();
} catch (IOException e) {
String msg = e.getMessage();
assertWithMessage(String.format("got %s want EBUSY or ENOTEMPTY", msg))
.that(
msg.endsWith(" (Directory not empty)")
|| msg.endsWith(" (Device or resource busy)")
|| msg.endsWith(" (Is a directory)"))
.isTrue(); // 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);
assertThat(isLaterThanreferenceTime(workingDir.getLastModifiedTime())).isTrue();
}
@Test
public void testRemoveFileChangesTimeOfDirectory() throws Exception {
Path newPath = absolutize("new-file");
FileSystemUtils.createEmptyFile(newPath);
storeReferenceTime(workingDir.getLastModifiedTime());
newPath.delete();
assertThat(isLaterThanreferenceTime(workingDir.getLastModifiedTime())).isTrue();
}
// 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);
assertThat(isLaterThanreferenceTime(newFile.getLastModifiedTime())).isTrue();
}
@Test
public void testCreateDirectoryTimestamp() throws Exception {
Path syncFile = absolutize("sync-file");
FileSystemUtils.createEmptyFile(syncFile);
Path newPath = absolutize("new-dir");
storeReferenceTime(syncFile.getLastModifiedTime());
assertThat(newPath.createDirectory()).isTrue();
assertThat(isLaterThanreferenceTime(newPath.getLastModifiedTime())).isTrue();
}
@Test
public void testWriteChangesModifiedTime() throws Exception {
storeReferenceTime(xFile.getLastModifiedTime());
FileSystemUtils.writeContentAsLatin1(xFile, "abc19");
assertThat(isLaterThanreferenceTime(xFile.getLastModifiedTime())).isTrue();
}
@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);
assertThat(xFile.getFileSize()).isEqualTo(testData.length());
}
// 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));
assertThat(resultData).isEqualTo(testData);
}
@Test
public void testInputAndOutputStreamEOF() throws Exception {
try (OutputStream outStream = xFile.getOutputStream()) {
outStream.write(1);
}
try (InputStream inStream = xFile.getInputStream()) {
inStream.read();
assertThat(inStream.read()).isEqualTo(-1);
}
}
@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();
assertThat(readValue).isEqualTo(i);
}
}
}
@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();
assertThat(readValue).isEqualTo(i);
}
}
}
@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()) {
assertThat(inStream.read()).isEqualTo(-1);
}
}
@Test
public void testGetOutputStreamCreatesFile() throws Exception {
Path newFile = absolutize("does_not_exist_yet.txt");
try (OutputStream out = newFile.getOutputStream()) {
out.write(42);
}
assertThat(newFile.isFile()).isTrue();
}
@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);
assertThat(xFile.exists()).isFalse();
assertThat(xNothing.isFile()).isTrue();
}
@Test
public void testCanRenameFileToExistingFile() throws Exception {
Path otherFile = absolutize("otherFile");
FileSystemUtils.createEmptyFile(otherFile);
xFile.renameTo(otherFile); // succeeds
assertThat(xFile.exists()).isFalse();
assertThat(otherFile.isFile()).isTrue();
}
@Test
public void testCanRenameDirToExistingEmptyDir() throws Exception {
xNonEmptyDirectory.renameTo(xEmptyDirectory); // succeeds
assertThat(xNonEmptyDirectory.exists()).isFalse();
assertThat(xEmptyDirectory.isDirectory()).isTrue();
assertThat(xEmptyDirectory.getDirectoryEntries()).isNotEmpty();
}
@Test
public void testCantRenameDirToExistingNonEmptyDir() throws Exception {
try {
xEmptyDirectory.renameTo(xNonEmptyDirectory);
fail();
} catch (IOException e) {
assertThat(e).hasMessageThat().endsWith(" (Directory not empty)");
}
}
@Test
public void testCantRenameDirToExistingNonEmptyDirNothingChanged() throws Exception {
try {
xEmptyDirectory.renameTo(xNonEmptyDirectory);
fail();
} catch (IOException e) {
// Expected
}
assertThat(xNonEmptyDirectory.isDirectory()).isTrue();
assertThat(xEmptyDirectory.isDirectory()).isTrue();
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
}
assertThat(xEmptyDirectory.isDirectory()).isTrue();
assertThat(xFile.isFile()).isTrue();
}
@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
}
assertThat(xEmptyDirectory.isDirectory()).isTrue();
assertThat(xFile.isFile()).isTrue();
}
@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).hasMessageThat().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 {
assertThat(xFile.isWritable()).isTrue();
}
@Test
public void testNewFilesAreReadable() throws Exception {
assertThat(xFile.isReadable()).isTrue();
}
@Test
public void testNewDirsAreWritable() throws Exception {
assertThat(xEmptyDirectory.isWritable()).isTrue();
}
@Test
public void testNewDirsAreReadable() throws Exception {
assertThat(xEmptyDirectory.isReadable()).isTrue();
}
@Test
public void testNewDirsAreExecutable() throws Exception {
assertThat(xEmptyDirectory.isExecutable()).isTrue();
}
@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);
assertThat(xFile.isReadable()).isFalse();
xFile.setReadable(true);
assertThat(xFile.isReadable()).isTrue();
}
@Test
public void testSetWritableOnFile() throws Exception {
xFile.setWritable(false);
assertThat(xFile.isWritable()).isFalse();
xFile.setWritable(true);
assertThat(xFile.isWritable()).isTrue();
}
@Test
public void testSetExecutableOnFile() throws Exception {
xFile.setExecutable(true);
assertThat(xFile.isExecutable()).isTrue();
xFile.setExecutable(false);
assertThat(xFile.isExecutable()).isFalse();
}
@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).hasMessageThat().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).hasMessageThat().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).hasMessageThat().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 (testFS.supportsSymbolicLinksNatively(xNonEmptyDirectoryBar)) {
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]);
assertThat(fp.hexDigestAndReset())
.isEqualTo(BaseEncoding.base16().lowerCase().encode(xFile.getDigest(HashFunction.MD5)));
}
@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);
assertThat(fp.hexDigestAndReset())
.isEqualTo(BaseEncoding.base16().lowerCase().encode(xFile.getDigest(HashFunction.MD5)));
}
@Test
public void testStatFailsFastOnNonExistingFiles() throws Exception {
try {
xNothing.stat();
fail("Expected IOException");
} catch(IOException e) {
// Do nothing.
}
}
@Test
public void testStatNullableFailsFastOnNonExistingFiles() throws Exception {
assertThat(xNothing.statNullable()).isNull();
}
@Test
public void testResolveSymlinks() throws Exception {
if (testFS.supportsSymbolicLinksNatively(xLink)) {
createSymbolicLink(xLink, xFile);
FileSystemUtils.createEmptyFile(xFile);
assertThat(testFS.resolveOneLink(xLink)).isEqualTo(xFile.asFragment());
assertThat(xLink.resolveSymbolicLinks()).isEqualTo(xFile);
}
}
@Test
public void testResolveDanglingSymlinks() throws Exception {
if (testFS.supportsSymbolicLinksNatively(xLink)) {
createSymbolicLink(xLink, xNothing);
assertThat(testFS.resolveOneLink(xLink)).isEqualTo(xNothing.asFragment());
try {
xLink.resolveSymbolicLinks();
fail();
} catch (IOException expected) {
}
}
}
@Test
public void testResolveNonSymlinks() throws Exception {
if (testFS.supportsSymbolicLinksNatively(xFile)) {
assertThat(testFS.resolveOneLink(xFile)).isNull();
assertThat(xFile.resolveSymbolicLinks()).isEqualTo(xFile);
}
}
@Test
public void testCreateHardLink_Success() throws Exception {
if (!testFS.supportsHardLinksNatively(xFile)) {
return;
}
xFile.createHardLink(xLink);
assertThat(xFile.exists()).isTrue();
assertThat(xLink.exists()).isTrue();
assertThat(xFile.isFile()).isTrue();
assertThat(xLink.isFile()).isTrue();
assertThat(isHardLinked(xFile, xLink)).isTrue();
}
@Test
public void testCreateHardLink_NeitherOriginalNorLinkExists() throws Exception {
if (!testFS.supportsHardLinksNatively(xFile)) {
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");
}
assertThat(xFile.exists()).isFalse();
assertThat(xLink.exists()).isFalse();
}
@Test
public void testCreateHardLink_OriginalDoesNotExistAndLinkExists() throws Exception {
if (!testFS.supportsHardLinksNatively(xFile)) {
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");
}
assertThat(xFile.exists()).isFalse();
assertThat(xLink.exists()).isTrue();
}
@Test
public void testCreateHardLink_BothOriginalAndLinkExist() throws Exception {
if (!testFS.supportsHardLinksNatively(xFile)) {
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");
}
assertThat(xFile.exists()).isTrue();
assertThat(xLink.exists()).isTrue();
assertThat(isHardLinked(xFile, xLink)).isFalse();
}
protected boolean isHardLinked(Path a, Path b) throws IOException {
return testFS.stat(a, false).getNodeId() == testFS.stat(b, false).getNodeId();
}
}