blob: 298b58842cd46cdcab812bb67f446dc27247a4d5 [file] [log] [blame]
/*
* Copyright 2013 Google Inc.
*
* 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.jimfs;
import static com.google.common.primitives.Bytes.concat;
import static com.google.jimfs.internal.attribute.UserLookupService.createUserPrincipal;
import static com.google.jimfs.testing.PathSubject.paths;
import static com.google.jimfs.testing.TestUtils.preFilledBytes;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardCopyOption.ATOMIC_MOVE;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.CREATE_NEW;
import static java.nio.file.StandardOpenOption.DSYNC;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.SPARSE;
import static java.nio.file.StandardOpenOption.SYNC;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
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 static org.truth0.Truth.ASSERT;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
import com.google.common.util.concurrent.Uninterruptibles;
import com.google.jimfs.testing.BasicFileAttribute;
import com.google.jimfs.testing.PathSubject;
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.io.Reader;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.DirectoryStream;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystemException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.NoSuchFileException;
import java.nio.file.NotDirectoryException;
import java.nio.file.NotLinkException;
import java.nio.file.Path;
import java.nio.file.SecureDirectoryStream;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.nio.file.attribute.UserPrincipal;
import java.util.Set;
import java.util.regex.PatternSyntaxException;
/**
* Tests an in-memory file system through the public APIs in {@link Files}, etc.
*
* @author Colin Decker
*/
public class JimfsIntegrationTest {
private FileSystem fs;
@Before
public void setUp() throws IOException {
fs = Jimfs.newUnixLikeFileSystem();
}
@Test
public void testPaths() {
assertThat("/").isAbsolute()
.and().hasRootComponent("/")
.and().hasNoNameComponents();
assertThat("foo").isRelative()
.and().hasNameComponents("foo");
assertThat("foo/bar").isRelative()
.and().hasNameComponents("foo", "bar");
assertThat("/foo/bar/baz").isAbsolute()
.and().hasRootComponent("/")
.and().hasNameComponents("foo", "bar", "baz");
}
@Test
public void testPaths_resolve() {
assertThat(path("/").resolve("foo/bar")).isAbsolute()
.and().hasRootComponent("/")
.and().hasNameComponents("foo", "bar");
assertThat(path("foo/bar").resolveSibling("baz")).isRelative()
.and().hasNameComponents("foo", "baz");
assertThat(path("foo/bar").resolve("/one/two")).isAbsolute()
.and().hasRootComponent("/")
.and().hasNameComponents("one", "two");
}
@Test
public void testPaths_normalize() {
assertThat(path("foo/bar/..").normalize()).isRelative()
.and().hasNameComponents("foo");
assertThat(path("foo/./bar/../baz/test/./../stuff").normalize()).isRelative()
.and().hasNameComponents("foo", "baz", "stuff");
assertThat(path("../../foo/./bar").normalize()).isRelative()
.and().hasNameComponents("..", "..", "foo", "bar");
assertThat(path("foo/../../bar").normalize()).isRelative()
.and().hasNameComponents("..", "bar");
assertThat(path(".././..").normalize()).isRelative()
.and().hasNameComponents("..", "..");
}
@Test
public void testPaths_relativize() {
assertThat(path("/foo/bar").relativize(path("/foo/bar/baz"))).isRelative()
.and().hasNameComponents("baz");
assertThat(path("/foo/bar/baz").relativize(path("/foo/bar"))).isRelative()
.and().hasNameComponents("..");
assertThat(path("/foo/bar/baz").relativize(path("/foo/baz/bar"))).isRelative()
.and().hasNameComponents("..", "..", "baz", "bar");
assertThat(path("foo/bar").relativize(path("foo"))).isRelative()
.and().hasNameComponents("..");
assertThat(path("foo").relativize(path("foo/bar"))).isRelative()
.and().hasNameComponents("bar");
try {
path("/foo/bar").relativize(path("bar"));
fail();
} catch (IllegalArgumentException expected) {
}
try {
path("bar").relativize(path("/foo/bar"));
fail();
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testPaths_startsWith_endsWith() {
ASSERT.that(path("/foo/bar").startsWith("/")).isTrue();
ASSERT.that(path("/foo/bar").startsWith("/foo")).isTrue();
ASSERT.that(path("/foo/bar").startsWith("/foo/bar")).isTrue();
ASSERT.that(path("/foo/bar").endsWith("bar")).isTrue();
ASSERT.that(path("/foo/bar").endsWith("foo/bar")).isTrue();
ASSERT.that(path("/foo/bar").endsWith("/foo/bar")).isTrue();
ASSERT.that(path("/foo/bar").endsWith("/foo")).isFalse();
ASSERT.that(path("/foo/bar").startsWith("foo/bar")).isFalse();
}
@Test
public void testPaths_toAbsolutePath() {
assertThat(path("/foo/bar").toAbsolutePath()).isAbsolute()
.and().hasRootComponent("/")
.and().hasNameComponents("foo", "bar")
.and().isEqualTo(path("/foo/bar"));
assertThat(path("foo/bar").toAbsolutePath()).isAbsolute()
.and().hasRootComponent("/")
.and().hasNameComponents("work", "foo", "bar")
.and().isEqualTo(path("/work/foo/bar"));
}
@Test
public void testPaths_toRealPath() throws IOException {
Files.createDirectories(path("/foo/bar"));
Files.createSymbolicLink(path("/link"), path("/"));
ASSERT.that(path("/link/foo/bar").toRealPath()).isEqualTo(path("/foo/bar"));
// case insensitivity tests: move to another test
//ASSERT.that(path("/FOO/BAR").toRealPath()).isEqualTo(path("/foo/bar"));
//ASSERT.that(path("/Link/FOO/bAR").toRealPath()).isEqualTo(path("/foo/bar"));
}
@Test
public void testPathMatchers_regex() {
assertThat("bar").matches("regex:.*");
assertThat("bar").matches("regex:bar");
assertThat("bar").matches("regex:[a-z]+");
assertThat("/foo/bar").matches("regex:/.*");
assertThat("/foo/bar").matches("regex:/.*/bar");
}
@Test
public void testPathMatchers_glob() {
assertThat("bar").matches("glob:bar");
assertThat("bar").matches("glob:*");
assertThat("/foo").doesNotMatch("glob:*");
assertThat("/foo/bar").doesNotMatch("glob:*");
assertThat("/foo/bar").matches("glob:**");
assertThat("/foo/bar").matches("glob:/**");
assertThat("foo/bar").doesNotMatch("glob:/**");
assertThat("/foo/bar/baz/stuff").matches("glob:/foo/**");
assertThat("/foo/bar/baz/stuff").matches("glob:/**/stuff");
assertThat("/foo").matches("glob:/[a-z]*");
assertThat("/Foo").doesNotMatch("glob:/[a-z]*");
assertThat("/foo/bar/baz/Stuff.java").matches("glob:**/*.java");
assertThat("/foo/bar/baz/Stuff.java").matches("glob:**/*.{java,class}");
assertThat("/foo/bar/baz/Stuff.class").matches("glob:**/*.{java,class}");
assertThat("/foo/bar/baz/Stuff.java").matches("glob:**/*.*");
try {
fs.getPathMatcher("glob:**/*.{java,class");
fail();
} catch (PatternSyntaxException expected) {
}
}
@Test
public void testPathMatchers_invalid() {
try {
fs.getPathMatcher("glob");
fail();
} catch (IllegalArgumentException expected) {
}
try {
fs.getPathMatcher("foo:foo");
fail();
} catch (UnsupportedOperationException expected) {
ASSERT.that(expected.getMessage()).contains("syntax");
}
}
@Test
public void testNewFileSystem_hasRootAndWorkingDirectory() throws IOException {
assertThat("/").hasChildren("work");
assertThat("/work").hasNoChildren();
}
@Test
public void testCreateDirectory_absolute() throws IOException {
Files.createDirectory(path("/test"));
assertThat("/test").exists();
assertThat("/").hasChildren("test", "work");
Files.createDirectory(path("/foo"));
Files.createDirectory(path("/foo/bar"));
assertThat("/foo/bar").exists();
assertThat("/foo").hasChildren("bar");
}
@Test
public void testCreateFile_absolute() throws IOException {
Files.createFile(path("/test.txt"));
assertThat("/test.txt").isRegularFile();
assertThat("/").hasChildren("test.txt", "work");
Files.createDirectory(path("/foo"));
Files.createFile(path("/foo/test.txt"));
assertThat("/foo/test.txt").isRegularFile();
assertThat("/foo").hasChildren("test.txt");
}
@Test
public void testCreateSymbolicLink_absolute() throws IOException {
Files.createSymbolicLink(path("/link.txt"), path("test.txt"));
assertThat("/link.txt", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("test.txt");
assertThat("/").hasChildren("link.txt", "work");
Files.createDirectory(path("/foo"));
Files.createSymbolicLink(path("/foo/link.txt"), path("test.txt"));
assertThat("/foo/link.txt").noFollowLinks()
.isSymbolicLink().withTarget("test.txt");
assertThat("/foo").hasChildren("link.txt");
}
@Test
public void testCreateLink_absolute() throws IOException {
Files.createFile(path("/test.txt"));
Files.createLink(path("/link.txt"), path("/test.txt"));
// don't assert that the link is the same file here, just that it was created
// later tests check that linking works correctly
assertThat("/link.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("/").hasChildren("link.txt", "test.txt", "work");
Files.createDirectory(path("/foo"));
Files.createLink(path("/foo/link.txt"), path("/test.txt"));
assertThat("/foo/link.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("/foo").hasChildren("link.txt");
}
@Test
public void testCreateDirectory_relative() throws IOException {
Files.createDirectory(path("test"));
assertThat("/work/test", NOFOLLOW_LINKS).isDirectory();
assertThat("test", NOFOLLOW_LINKS).isDirectory();
assertThat("/work").hasChildren("test");
assertThat("test").isSameFileAs("/work/test");
Files.createDirectory(path("foo"));
Files.createDirectory(path("foo/bar"));
assertThat("/work/foo/bar", NOFOLLOW_LINKS).isDirectory();
assertThat("foo/bar", NOFOLLOW_LINKS).isDirectory();
assertThat("/work/foo").hasChildren("bar");
assertThat("foo").hasChildren("bar");
assertThat("foo/bar").isSameFileAs("/work/foo/bar");
}
@Test
public void testCreateFile_relative() throws IOException {
Files.createFile(path("test.txt"));
assertThat("/work/test.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("test.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("/work").hasChildren("test.txt");
assertThat("test.txt").isSameFileAs("/work/test.txt");
Files.createDirectory(path("foo"));
Files.createFile(path("foo/test.txt"));
assertThat("/work/foo/test.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("foo/test.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("/work/foo").hasChildren("test.txt");
assertThat("foo").hasChildren("test.txt");
assertThat("foo/test.txt").isSameFileAs("/work/foo/test.txt");
}
@Test
public void testCreateSymbolicLink_relative() throws IOException {
Files.createSymbolicLink(path("link.txt"), path("test.txt"));
assertThat("/work/link.txt", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("test.txt");
assertThat("link.txt", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("test.txt");
assertThat("/work").hasChildren("link.txt");
Files.createDirectory(path("foo"));
Files.createSymbolicLink(path("foo/link.txt"), path("test.txt"));
assertThat("/work/foo/link.txt", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("test.txt");
assertThat("foo/link.txt", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("test.txt");
assertThat("/work/foo").hasChildren("link.txt");
assertThat("foo").hasChildren("link.txt");
}
@Test
public void testCreateLink_relative() throws IOException {
Files.createFile(path("test.txt"));
Files.createLink(path("link.txt"), path("test.txt"));
// don't assert that the link is the same file here, just that it was created
// later tests check that linking works correctly
assertThat("/work/link.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("link.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("/work").hasChildren("link.txt", "test.txt");
Files.createDirectory(path("foo"));
Files.createLink(path("foo/link.txt"), path("test.txt"));
assertThat("/work/foo/link.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("foo/link.txt", NOFOLLOW_LINKS).isRegularFile();
assertThat("foo").hasChildren("link.txt");
}
@Test
public void testCreateFile_existing() throws IOException {
Files.createFile(path("/test"));
try {
Files.createFile(path("/test"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/test", expected.getMessage());
}
try {
Files.createDirectory(path("/test"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/test", expected.getMessage());
}
try {
Files.createSymbolicLink(path("/test"), path("/foo"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/test", expected.getMessage());
}
Files.createFile(path("/foo"));
try {
Files.createLink(path("/test"), path("/foo"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/test", expected.getMessage());
}
}
@Test
public void testCreateFile_parentDoesNotExist() throws IOException {
try {
Files.createFile(path("/foo/test"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/foo/test", expected.getMessage());
}
try {
Files.createDirectory(path("/foo/test"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/foo/test", expected.getMessage());
}
try {
Files.createSymbolicLink(path("/foo/test"), path("/bar"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/foo/test", expected.getMessage());
}
Files.createFile(path("/bar"));
try {
Files.createLink(path("/foo/test"), path("/bar"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/foo/test", expected.getMessage());
}
}
@Test
public void testCreateFile_parentIsNotDirectory() throws IOException {
Files.createDirectory(path("/foo"));
Files.createFile(path("/foo/bar"));
try {
Files.createFile(path("/foo/bar/baz"));
fail();
} catch (NoSuchFileException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo/bar/baz");
}
}
@Test
public void testCreateFile_nonDirectoryHigherInPath() throws IOException {
Files.createDirectory(path("/foo"));
Files.createFile(path("/foo/bar"));
try {
Files.createFile(path("/foo/bar/baz/stuff"));
fail();
} catch (NoSuchFileException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo/bar/baz/stuff");
}
}
@Test
public void testCreateFile_parentSymlinkDoesNotExist() throws IOException {
Files.createDirectory(path("/foo"));
Files.createSymbolicLink(path("/foo/bar"), path("/foo/nope"));
try {
Files.createFile(path("/foo/bar/baz"));
fail();
} catch (NoSuchFileException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo/bar/baz");
}
}
@Test
public void testCreateFile_symlinkHigherInPathDoesNotExist() throws IOException {
Files.createDirectory(path("/foo"));
Files.createSymbolicLink(path("/foo/bar"), path("nope"));
try {
Files.createFile(path("/foo/bar/baz/stuff"));
fail();
} catch (NoSuchFileException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo/bar/baz/stuff");
}
}
@Test
public void testCreateFile_parentSymlinkDoesPointsToNonDirectory() throws IOException {
Files.createDirectory(path("/foo"));
Files.createFile(path("/foo/file"));
Files.createSymbolicLink(path("/foo/bar"), path("/foo/file"));
try {
Files.createFile(path("/foo/bar/baz"));
fail();
} catch (NoSuchFileException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo/bar/baz");
}
}
@Test
public void testCreateFile_symlinkHigherInPathPointsToNonDirectory() throws IOException {
Files.createDirectory(path("/foo"));
Files.createFile(path("/foo/file"));
Files.createSymbolicLink(path("/foo/bar"), path("file"));
try {
Files.createFile(path("/foo/bar/baz/stuff"));
fail();
} catch (NoSuchFileException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo/bar/baz/stuff");
}
}
@Test
public void testCreateFile_withInitialAttributes() throws IOException {
Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("rwxrwxrwx");
FileAttribute<?> permissionsAttr = PosixFilePermissions.asFileAttribute(permissions);
Files.createFile(path("/normal"));
Files.createFile(path("/foo"), permissionsAttr);
assertThat("/normal").attribute("posix:permissions").isNot(permissions);
assertThat("/foo").attribute("posix:permissions").is(permissions);
FileAttribute<UserPrincipal> ownerAttr =
new BasicFileAttribute<>("posix:owner", createUserPrincipal("foo"));
Files.createFile(path("/foo2"), ownerAttr, permissionsAttr);
assertThat("/normal").attribute("owner:owner").isNot(ownerAttr.value());
assertThat("/foo2").attribute("owner:owner").is(ownerAttr.value());
assertThat("/foo2").attribute("posix:permissions").is(permissions);
}
@Test
public void testCreateFile_withInitialAttributes_illegalInitialAttribute() throws IOException {
try {
Files.createFile(path("/foo"),
new BasicFileAttribute<>("basic:lastModifiedTime", FileTime.fromMillis(0L)));
} catch (UnsupportedOperationException expected) {
}
assertThat("/foo").doesNotExist();
try {
Files.createFile(path("/foo"),
new BasicFileAttribute<>("basic:noSuchAttribute", "foo"));
} catch (IllegalArgumentException expected) {
}
assertThat("/foo").doesNotExist();
}
@Test
public void testOpenChannel_withInitialAttributes_createNewFile() throws IOException {
FileAttribute<Set<PosixFilePermission>> permissions = PosixFilePermissions.asFileAttribute(
PosixFilePermissions.fromString("rwxrwxrwx"));
Files.newByteChannel(path("/foo"), ImmutableSet.of(CREATE), permissions).close();
assertThat("/foo").isRegularFile()
.and().attribute("posix:permissions").is(permissions.value());
}
@Test
public void testOpenChannel_withInitialAttributes_fileExists() throws IOException {
Files.createFile(path("/foo"));
FileAttribute<Set<PosixFilePermission>> permissions = PosixFilePermissions.asFileAttribute(
PosixFilePermissions.fromString("rwxrwxrwx"));
Files.newByteChannel(path("/foo"), ImmutableSet.of(CREATE), permissions).close();
assertThat("/foo").isRegularFile()
.and().attribute("posix:permissions").isNot(permissions.value());
}
@Test
public void testCreateDirectory_withInitialAttributes() throws IOException {
FileAttribute<Set<PosixFilePermission>> permissions = PosixFilePermissions.asFileAttribute(
PosixFilePermissions.fromString("rwxrwxrwx"));
Files.createDirectory(path("/foo"), permissions);
assertThat("/foo").isDirectory()
.and().attribute("posix:permissions").is(permissions.value());
Files.createDirectory(path("/normal"));
assertThat("/normal").isDirectory()
.and().attribute("posix:permissions").isNot(permissions.value());
}
@Test
public void testCreateSymbolicLink_withInitialAttributes() throws IOException {
FileAttribute<Set<PosixFilePermission>> permissions = PosixFilePermissions.asFileAttribute(
PosixFilePermissions.fromString("rwxrwxrwx"));
Files.createSymbolicLink(path("/foo"), path("bar"), permissions);
assertThat("/foo", NOFOLLOW_LINKS).isSymbolicLink()
.and().attribute("posix:permissions").is(permissions.value());
Files.createSymbolicLink(path("/normal"), path("bar"));
assertThat("/normal", NOFOLLOW_LINKS).isSymbolicLink()
.and().attribute("posix:permissions").isNot(permissions.value());
}
@Test
public void testCreateDirectories() throws IOException {
Files.createDirectories(path("/foo/bar/baz"));
assertThat("/foo").isDirectory();
assertThat("/foo/bar").isDirectory();
assertThat("/foo/bar/baz").isDirectory();
Files.createDirectories(path("/foo/asdf/jkl"));
assertThat("/foo/asdf").isDirectory();
assertThat("/foo/asdf/jkl").isDirectory();
Files.createDirectories(path("bar/baz"));
assertThat("bar/baz").isDirectory();
assertThat("/work/bar/baz").isDirectory();
}
@Test
public void testDirectories_newlyCreatedDirectoryHasTwoLinks() throws IOException {
// one link from its parent to it; one from it to itself
Files.createDirectory(path("/foo"));
assertThat("/foo").hasLinkCount(2);
}
@Test
public void testDirectories_creatingDirectoryAddsOneLinkToParent() throws IOException {
// from the .. direntry
Files.createDirectory(path("/foo"));
Files.createDirectory(path("/foo/bar"));
assertThat("/foo").hasLinkCount(3);
Files.createDirectory(path("/foo/baz"));
assertThat("/foo").hasLinkCount(4);
}
@Test
public void testDirectories_creatingNonDirectoryDoesNotAddLinkToParent() throws IOException {
Files.createDirectory(path("/foo"));
Files.createFile(path("/foo/file"));
Files.createSymbolicLink(path("/foo/fileSymlink"), path("file"));
Files.createLink(path("/foo/link"), path("/foo/file"));
Files.createSymbolicLink(path("/foo/fooSymlink"), path("/foo"));
assertThat("/foo").hasLinkCount(2);
}
@Test
public void testSize_forNewFile_isZero() throws IOException {
Files.createFile(path("/test"));
assertThat("/test").hasSize(0);
}
@Test
public void testRead_forNewFile_isEmpty() throws IOException {
Files.createFile(path("/test"));
assertThat("/test").containsNoBytes();
}
@Test
public void testWriteFile_succeeds() throws IOException {
Files.createFile(path("/test"));
Files.write(path("/test"), new byte[]{0, 1, 2, 3});
}
@Test
public void testSize_forFileAfterWrite_isNumberOfBytesWritten() throws IOException {
Files.write(path("/test"), new byte[]{0, 1, 2, 3});
assertThat("/test").hasSize(4);
}
@Test
public void testRead_forFileAfterWrite_isBytesWritten() throws IOException {
byte[] bytes = {0, 1, 2, 3};
Files.write(path("/test"), bytes);
assertThat("/test").containsBytes(bytes);
}
@Test
public void testWriteFile_withStandardOptions() throws IOException {
Path test = path("/test");
byte[] bytes = {0, 1, 2, 3};
try {
// CREATE and CREATE_NEW not specified
Files.write(test, bytes, WRITE);
fail();
} catch (NoSuchFileException expected) {
assertEquals(test.toString(), expected.getMessage());
}
Files.write(test, bytes, CREATE_NEW); // succeeds, file does not exist
assertThat("/test").containsBytes(bytes);
try {
Files.write(test, bytes, CREATE_NEW); // CREATE_NEW requires file not exist
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals(test.toString(), expected.getMessage());
}
Files.write(test, new byte[]{4, 5}, CREATE); // succeeds, ok for file to already exist
assertThat("/test").containsBytes(4, 5, 2, 3); // did not truncate or append, so overwrote
Files.write(test, bytes, WRITE, CREATE, TRUNCATE_EXISTING); // default options
assertThat("/test").containsBytes(bytes);
Files.write(test, bytes, WRITE, APPEND);
assertThat("/test").containsBytes(0, 1, 2, 3, 0, 1, 2, 3);
Files.write(test, bytes, WRITE, CREATE, TRUNCATE_EXISTING, APPEND, SPARSE, DSYNC, SYNC);
assertThat("/test").containsBytes(bytes);
try {
Files.write(test, bytes, READ, WRITE); // READ not allowed
fail();
} catch (UnsupportedOperationException expected) {}
}
@Test
public void testWriteLines_succeeds() throws IOException {
Files.write(path("/test.txt"), ImmutableList.of("hello", "world"), UTF_8);
}
@Test
public void testRead_forFileAfterWriteLines_isLinesWritten() throws IOException {
Files.write(path("/test.txt"), ImmutableList.of("hello", "world"), UTF_8);
assertThat("/test.txt").containsLines("hello", "world");
}
@Test
public void testWriteLines_withStandardOptions() throws IOException {
Path test = path("/test.txt");
ImmutableList<String> lines = ImmutableList.of("hello", "world");
try {
// CREATE and CREATE_NEW not specified
Files.write(test, lines, UTF_8, WRITE);
fail();
} catch (NoSuchFileException expected) {
assertEquals(test.toString(), expected.getMessage());
}
Files.write(test, lines, UTF_8, CREATE_NEW); // succeeds, file does not exist
assertThat(test).containsLines(lines);
try {
Files.write(test, lines, UTF_8, CREATE_NEW); // CREATE_NEW requires file not exist
fail();
} catch (FileAlreadyExistsException expected) {}
// succeeds, ok for file to already exist
Files.write(test, ImmutableList.of("foo"), UTF_8, CREATE);
// did not truncate or append, so overwrote
if ("\r\n".equals(System.getProperty("line.separator"))) {
// on Windows, an extra character is overwritten by the \r\n line separator
assertThat(test).containsLines("foo", "", "world");
} else {
assertThat(test).containsLines("foo", "o", "world");
}
Files.write(test, lines, UTF_8, WRITE, CREATE, TRUNCATE_EXISTING); // default options
assertThat(test).containsLines(lines);
Files.write(test, lines, UTF_8, WRITE, APPEND);
assertThat(test).containsLines("hello", "world", "hello", "world");
Files.write(test, lines, UTF_8, WRITE, CREATE, TRUNCATE_EXISTING, APPEND, SPARSE, DSYNC, SYNC);
assertThat(test).containsLines(lines);
try {
Files.write(test, lines, UTF_8, READ, WRITE); // READ not allowed
fail();
} catch (UnsupportedOperationException expected) {}
}
@Test
public void testWrite_fileExistsButIsNotRegularFile() throws IOException {
Files.createDirectory(path("/foo"));
try {
// non-CREATE mode
Files.write(path("/foo"), preFilledBytes(10), WRITE);
} catch (FileSystemException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo");
ASSERT.that(expected.getMessage()).contains("regular file");
}
try {
// CREATE mode
Files.write(path("/foo"), preFilledBytes(10));
} catch (FileSystemException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo");
ASSERT.that(expected.getMessage()).contains("regular file");
}
}
@Test
public void testDelete_file() throws IOException {
try {
Files.delete(path("/test"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/test", expected.getMessage());
}
try {
Files.delete(path("/foo/bar"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/foo/bar", expected.getMessage());
}
assertFalse(Files.deleteIfExists(path("/test")));
assertFalse(Files.deleteIfExists(path("/foo/bar")));
Files.createFile(path("/test"));
assertThat("/test").isRegularFile();
Files.delete(path("/test"));
assertThat("/test").doesNotExist();
Files.createFile(path("/test"));
assertTrue(Files.deleteIfExists(path("/test")));
assertThat("/test").doesNotExist();
}
@Test
public void testDelete_directory() throws IOException {
Files.createDirectories(path("/foo/bar"));
assertThat("/foo").isDirectory();
assertThat("/foo/bar").isDirectory();
Files.delete(path("/foo/bar"));
assertThat("/foo/bar").doesNotExist();
assertTrue(Files.deleteIfExists(path("/foo")));
assertThat("/foo").doesNotExist();
}
@Test
public void testDelete_directory_cantDeleteNonEmptyDirectory() throws IOException {
Files.createDirectories(path("/foo/bar"));
try {
Files.delete(path("/foo"));
fail();
} catch (DirectoryNotEmptyException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo");
}
try {
Files.deleteIfExists(path("/foo"));
fail();
} catch (DirectoryNotEmptyException expected) {
ASSERT.that(expected.getFile()).isEqualTo("/foo");
}
}
@Test
public void testDelete_directory_cantDeleteRoot() throws IOException {
// delete working directory so that root is empty
// don't want to just be testing the "can't delete when not empty" logic
// TODO(cgdecker): it's possible that deleting the working directory should also fail
// it seems to fail in the default file system, and deleting it does permanently cause all
// relative path operations to fail even if a new directory is created at the same path
Files.delete(path("/work"));
try {
Files.delete(path("/"));
fail();
} catch (IOException expected) {
ASSERT.that(expected.getMessage()).contains("root");
}
Files.createDirectories(path("/foo/bar"));
try {
Files.delete(path("/foo/bar/../.."));
fail();
} catch (IOException expected) {
ASSERT.that(expected.getMessage()).contains("root");
}
try {
Files.delete(path("/foo/./../foo/bar/./../bar/.././../../.."));
fail();
} catch (IOException expected) {
ASSERT.that(expected.getMessage()).contains("root");
}
}
@Test
public void testSymbolicLinks() throws IOException {
Files.createSymbolicLink(path("/link.txt"), path("/file.txt"));
assertThat("/link.txt", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("/file.txt");
assertThat("/link.txt").doesNotExist(); // following the link; target doesn't exist
try {
Files.createFile(path("/link.txt"));
fail();
} catch (FileAlreadyExistsException expected) {}
try {
Files.readAllBytes(path("/link.txt"));
fail();
} catch (NoSuchFileException expected) {}
Files.createFile(path("/file.txt"));
assertThat("/link.txt").isRegularFile(); // following the link; target does exist
assertThat("/link.txt").containsNoBytes();
Files.createSymbolicLink(path("/foo"), path("/bar/baz"));
assertThat("/foo", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("/bar/baz");
assertThat("/foo").doesNotExist(); // following the link; target doesn't exist
Files.createDirectories(path("/bar/baz"));
assertThat("/foo").isDirectory(); // following the link; target does exist
Files.createFile(path("/bar/baz/test.txt"));
assertThat("/foo/test.txt", NOFOLLOW_LINKS).isRegularFile(); // follow intermediate link
try {
Files.readSymbolicLink(path("/none"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/none", expected.getMessage());
}
try {
Files.readSymbolicLink(path("/file.txt"));
fail();
} catch (NotLinkException expected) {
assertEquals("/file.txt", expected.getMessage());
}
}
@Test
public void testSymbolicLinks_symlinkCycle() throws IOException {
Files.createDirectory(path("/foo"));
Files.createSymbolicLink(path("/foo/bar"), path("baz"));
Files.createSymbolicLink(path("/foo/baz"), path("bar"));
try {
Files.createFile(path("/foo/bar/file"));
fail();
} catch (IOException expected) {
ASSERT.that(expected.getMessage()).contains("symbolic link");
}
try {
Files.write(path("/foo/bar"), preFilledBytes(10));
fail();
} catch (IOException expected) {
ASSERT.that(expected.getMessage()).contains("symbolic link");
}
}
@Test
public void testSymbolicLinks_lookupOfAbsoluteSymlinkPathFromRelativePath() throws IOException {
// relative path lookups are in the FileTree for the working directory
// this tests that when an absolute path is encountered, the lookup switches to the super root
// FileTree
Files.createDirectories(path("/foo/bar/baz"));
Files.createFile(path("/foo/bar/baz/file"));
Files.createDirectories(path("one/two/three"));
Files.createSymbolicLink(path("/work/one/two/three/link"), path("/foo/bar"));
assertThat("one/two/three/link/baz/file")
.isSameFileAs("/foo/bar/baz/file");
}
@Test
public void testLink() throws IOException {
Files.createFile(path("/file.txt"));
// checking link count requires "unix" attribute support, which we're using here
assertThat("/file.txt").hasLinkCount(1);
Files.createLink(path("/link.txt"), path("/file.txt"));
assertThat("/link.txt").isSameFileAs("/file.txt");
assertThat("/file.txt").hasLinkCount(2);
assertThat("/link.txt").hasLinkCount(2);
assertThat("/file.txt").containsNoBytes();
assertThat("/link.txt").containsNoBytes();
byte[] bytes = {0, 1, 2, 3};
Files.write(path("/file.txt"), bytes);
assertThat("/file.txt").containsBytes(bytes);
assertThat("/link.txt").containsBytes(bytes);
Files.write(path("/link.txt"), bytes, APPEND);
assertThat("/file.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3);
assertThat("/link.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3);
Files.delete(path("/file.txt"));
assertThat("/link.txt").hasLinkCount(1);
assertThat("/link.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3);
}
@Test
public void testLink_forSymbolicLink_usesSymbolicLinkTarget() throws IOException {
Files.createFile(path("/file"));
Files.createSymbolicLink(path("/symlink"), path("/file"));
Object key = getFileKey("/file");
Files.createLink(path("/link"), path("/symlink"));
assertThat("/link").isRegularFile()
.and().hasLinkCount(2)
.and().attribute("fileKey").is(key);
}
@Test
public void testLink_failsWhenTargetDoesNotExist() throws IOException {
try {
Files.createLink(path("/link"), path("/foo"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/foo", expected.getFile());
}
Files.createSymbolicLink(path("/foo"), path("/bar"));
try {
Files.createLink(path("/link"), path("/foo"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/foo", expected.getFile());
}
}
@Test
public void testLink_failsForNonRegularFile() throws IOException {
Files.createDirectory(path("/dir"));
try {
Files.createLink(path("/link"), path("/dir"));
fail();
} catch (FileSystemException expected) {
assertEquals("/link", expected.getFile());
assertEquals("/dir", expected.getOtherFile());
}
assertThat("/link").doesNotExist();
}
@Test
public void testLinks_failsWhenTargetFileAlreadyExists() throws IOException {
Files.createFile(path("/file"));
Files.createFile(path("/link"));
try {
Files.createLink(path("/link"), path("/file"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/link", expected.getFile());
}
}
@Test
public void testStreams() throws IOException {
try (OutputStream out = Files.newOutputStream(path("/test"))) {
for (int i = 0; i < 100; i++) {
out.write(i);
}
}
byte[] expected = new byte[100];
for (byte i = 0; i < 100; i++) {
expected[i] = i;
}
try (InputStream in = Files.newInputStream(path("/test"))) {
byte[] bytes = new byte[100];
ByteStreams.readFully(in, bytes);
assertArrayEquals(expected, bytes);
}
try (Writer writer = Files.newBufferedWriter(path("/test.txt"), UTF_8)) {
writer.write("hello");
}
try (Reader reader = Files.newBufferedReader(path("/test.txt"), UTF_8)) {
assertEquals("hello", CharStreams.toString(reader));
}
try (Writer writer = Files.newBufferedWriter(path("/test.txt"), UTF_8, APPEND)) {
writer.write(" world");
}
try (Reader reader = Files.newBufferedReader(path("/test.txt"), UTF_8)) {
assertEquals("hello world", CharStreams.toString(reader));
}
}
@Test
public void testChannels() throws IOException {
try (FileChannel channel = FileChannel.open(path("/test.txt"), CREATE_NEW, WRITE)) {
ByteBuffer buf1 = UTF_8.encode("hello");
ByteBuffer buf2 = UTF_8.encode(" world");
while (buf1.hasRemaining() || buf2.hasRemaining()) {
channel.write(new ByteBuffer[]{buf1, buf2});
}
}
try (SeekableByteChannel channel = Files.newByteChannel(path("/test.txt"), READ)) {
ByteBuffer buffer = ByteBuffer.allocate(100);
while (channel.read(buffer) != -1) {
}
buffer.flip();
assertEquals("hello world", UTF_8.decode(buffer).toString());
}
byte[] bytes = preFilledBytes(100);
Files.write(path("/test"), bytes);
try (SeekableByteChannel channel = Files.newByteChannel(path("/test"), READ, WRITE)) {
ByteBuffer buffer = ByteBuffer.wrap(preFilledBytes(50));
channel.position(50);
channel.write(buffer);
buffer.flip();
channel.write(buffer);
channel.position(0);
ByteBuffer readBuffer = ByteBuffer.allocate(150);
while (readBuffer.hasRemaining()) {
channel.read(readBuffer);
}
byte[] expected = concat(preFilledBytes(50), preFilledBytes(50), preFilledBytes(50));
assertArrayEquals(expected, readBuffer.array());
}
try (FileChannel channel = FileChannel.open(path("/test"), READ, WRITE)) {
assertEquals(150, channel.size());
channel.truncate(10);
assertEquals(10, channel.size());
ByteBuffer buffer = ByteBuffer.allocate(20);
assertEquals(10, channel.read(buffer));
buffer.flip();
byte[] expected = new byte[20];
System.arraycopy(preFilledBytes(10), 0, expected, 0, 10);
assertArrayEquals(expected, buffer.array());
}
}
@Test
public void testCopy_inputStreamToFile() throws IOException {
byte[] bytes = preFilledBytes(512);
Files.copy(new ByteArrayInputStream(bytes), path("/test"));
assertThat("/test").containsBytes(bytes);
try {
Files.copy(new ByteArrayInputStream(bytes), path("/test"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/test", expected.getMessage());
}
Files.copy(new ByteArrayInputStream(bytes), path("/test"), REPLACE_EXISTING);
assertThat("/test").containsBytes(bytes);
Files.copy(new ByteArrayInputStream(bytes), path("/foo"), REPLACE_EXISTING);
assertThat("/foo").containsBytes(bytes);
}
@Test
public void testCopy_fileToOutputStream() throws IOException {
byte[] bytes = preFilledBytes(512);
Files.write(path("/test"), bytes);
ByteArrayOutputStream out = new ByteArrayOutputStream();
Files.copy(path("/test"), out);
assertArrayEquals(bytes, out.toByteArray());
}
@Test
public void testCopy_fileToPath() throws IOException {
byte[] bytes = preFilledBytes(512);
Files.write(path("/foo"), bytes);
assertThat("/bar").doesNotExist();
Files.copy(path("/foo"), path("/bar"));
assertThat("/bar").containsBytes(bytes);
byte[] moreBytes = preFilledBytes(2048);
Files.write(path("/baz"), moreBytes);
Files.copy(path("/baz"), path("/bar"), REPLACE_EXISTING);
assertThat("/bar").containsBytes(moreBytes);
try {
Files.copy(path("/none"), path("/bar"));
fail();
} catch (NoSuchFileException expected) {
assertEquals("/none", expected.getMessage());
}
}
@Test
public void testCopy_doesNotSupportAtomicMove() throws IOException {
try {
Files.copy(path("/foo"), path("/bar"), ATOMIC_MOVE);
fail();
} catch (UnsupportedOperationException expected) {
}
}
@Test
public void testCopy_directoryToPath() throws IOException {
Files.createDirectory(path("/foo"));
assertThat("/bar").doesNotExist();
Files.copy(path("/foo"), path("/bar"));
assertThat("/bar").isDirectory();
}
@Test
public void testCopy_withoutReplaceExisting_failsWhenTargetExists() throws IOException {
Files.createFile(path("/bar"));
Files.createDirectory(path("/foo"));
// dir -> file
try {
Files.copy(path("/foo"), path("/bar"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/bar", expected.getMessage());
}
Files.delete(path("/foo"));
Files.createFile(path("/foo"));
// file -> file
try {
Files.copy(path("/foo"), path("/bar"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/bar", expected.getMessage());
}
Files.delete(path("/bar"));
Files.createDirectory(path("/bar"));
// file -> dir
try {
Files.copy(path("/foo"), path("/bar"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/bar", expected.getMessage());
}
Files.delete(path("/foo"));
Files.createDirectory(path("/foo"));
// dir -> dir
try {
Files.copy(path("/foo"), path("/bar"));
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/bar", expected.getMessage());
}
}
@Test
public void testCopy_withReplaceExisting() throws IOException {
Files.createFile(path("/bar"));
Files.createDirectory(path("/test"));
assertThat("/bar").isRegularFile();
// overwrite regular file w/ directory
Files.copy(path("/test"), path("/bar"), REPLACE_EXISTING);
assertThat("/bar").isDirectory();
byte[] bytes = {0, 1, 2, 3};
Files.write(path("/baz"), bytes);
// overwrite directory w/ regular file
Files.copy(path("/baz"), path("/bar"), REPLACE_EXISTING);
assertThat("/bar").containsSameBytesAs("/baz");
}
@Test
public void testCopy_withReplaceExisting_cantReplaceNonEmptyDirectory() throws IOException {
Files.createDirectory(path("/foo"));
Files.createDirectory(path("/foo/bar"));
Files.createFile(path("/foo/baz"));
Files.createDirectory(path("/test"));
try {
Files.copy(path("/test"), path("/foo"), REPLACE_EXISTING);
fail();
} catch (DirectoryNotEmptyException expected) {
assertEquals("/foo", expected.getMessage());
}
Files.delete(path("/test"));
Files.createFile(path("/test"));
try {
Files.copy(path("/test"), path("/foo"), REPLACE_EXISTING);
fail();
} catch (DirectoryNotEmptyException expected) {
assertEquals("/foo", expected.getMessage());
}
Files.delete(path("/foo/baz"));
Files.delete(path("/foo/bar"));
Files.copy(path("/test"), path("/foo"), REPLACE_EXISTING);
assertThat("/foo").isRegularFile(); // replaced
}
@Test
public void testCopy_directoryToPath_doesNotCopyDirectoryContents() throws IOException {
Files.createDirectory(path("/foo"));
Files.createDirectory(path("/foo/baz"));
Files.createFile(path("/foo/test"));
Files.copy(path("/foo"), path("/bar"));
assertThat("/bar").hasNoChildren();
}
@Test
public void testCopy_symbolicLinkToPath() throws IOException {
byte[] bytes = preFilledBytes(128);
Files.write(path("/test"), bytes);
Files.createSymbolicLink(path("/link"), path("/test"));
assertThat("/bar").doesNotExist();
Files.copy(path("/link"), path("/bar"));
assertThat("/bar", NOFOLLOW_LINKS).containsBytes(bytes);
Files.delete(path("/bar"));
Files.copy(path("/link"), path("/bar"), NOFOLLOW_LINKS);
assertThat("/bar", NOFOLLOW_LINKS)
.isSymbolicLink().withTarget("/test");
assertThat("/bar").isRegularFile();
assertThat("/bar").containsBytes(bytes);
Files.delete(path("/test"));
assertThat("/bar", NOFOLLOW_LINKS).isSymbolicLink();
assertThat("/bar").doesNotExist();
}
@Test
public void testMove() throws IOException {
byte[] bytes = preFilledBytes(100);
Files.write(path("/foo"), bytes);
Object fooKey = getFileKey("/foo");
Files.move(path("/foo"), path("/bar"));
assertThat("/foo").doesNotExist()
.andThat("/bar")
.containsBytes(bytes).and()
.attribute("fileKey").is(fooKey);
Files.createDirectory(path("/foo"));
Files.move(path("/bar"), path("/foo/bar"));
assertThat("/bar").doesNotExist()
.andThat("/foo/bar").isRegularFile();
Files.move(path("/foo"), path("/baz"));
assertThat("/foo").doesNotExist()
.andThat("/baz").isDirectory()
.andThat("/baz/bar").isRegularFile();
}
@Test
public void testMove_cannotMoveDirIntoOwnSubtree() throws IOException {
Files.createDirectories(path("/foo"));
try {
Files.move(path("/foo"), path("/foo/bar"));
fail();
} catch (IOException expected) {
ASSERT.that(expected.getMessage()).contains("sub");
}
Files.createDirectories(path("/foo/bar/baz/stuff"));
Files.createDirectories(path("/hello/world"));
Files.createSymbolicLink(path("/hello/world/link"), path("../../foo/bar/baz"));
try {
Files.move(path("/foo/bar"), path("/hello/world/link/bar"));
fail();
} catch (IOException expected) {
ASSERT.that(expected.getMessage()).contains("sub");
}
}
@Test
public void testMove_withoutReplaceExisting_failsWhenTargetExists() throws IOException {
byte[] bytes = preFilledBytes(50);
Files.write(path("/test"), bytes);
Object testKey = getFileKey("/test");
Files.createFile(path("/bar"));
try {
Files.move(path("/test"), path("/bar"), ATOMIC_MOVE);
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/bar", expected.getMessage());
}
assertThat("/test")
.containsBytes(bytes).and()
.attribute("fileKey").is(testKey);
Files.delete(path("/bar"));
Files.createDirectory(path("/bar"));
try {
Files.move(path("/test"), path("/bar"), ATOMIC_MOVE);
fail();
} catch (FileAlreadyExistsException expected) {
assertEquals("/bar", expected.getMessage());
}
assertThat("/test")
.containsBytes(bytes).and()
.attribute("fileKey").is(testKey);
}
@Test
public void testIsSameFile() throws IOException {
Files.createDirectory(path("/foo"));
Files.createSymbolicLink(path("/bar"), path("/foo"));
Files.createFile(path("/bar/test"));
assertThat("/foo").isSameFileAs("/foo");
assertThat("/bar").isSameFileAs("/bar");
assertThat("/foo/test").isSameFileAs("/foo/test");
assertThat("/bar/test").isSameFileAs("/bar/test");
assertThat("/foo").isNotSameFileAs("test");
assertThat("/bar").isNotSameFileAs("/test");
assertThat("/foo").isSameFileAs("/bar");
assertThat("/foo/test").isSameFileAs("/bar/test");
Files.createSymbolicLink(path("/baz"), path("bar")); // relative path
assertThat("/baz").isSameFileAs("/foo");
assertThat("/baz/test").isSameFileAs("/foo/test");
}
@Test
public void testIsSameFile_forPathFromDifferentFileSystemProvider() throws IOException {
Path defaultFileSystemRoot = FileSystems.getDefault()
.getRootDirectories().iterator().next();
ASSERT.that(Files.isSameFile(path("/"), defaultFileSystemRoot)).isFalse();
}
@Test
public void testPathLookups() throws IOException {
assertThat("/").isSameFileAs("/");
assertThat("/..").isSameFileAs("/");
assertThat("/../../..").isSameFileAs("/");
assertThat("../../../..").isSameFileAs("/");
assertThat("").isSameFileAs("/work");
Files.createDirectories(path("/foo/bar/baz"));
Files.createSymbolicLink(path("/foo/bar/link1"), path("../link2"));
Files.createSymbolicLink(path("/foo/link2"), path("/"));
assertThat("/foo/bar/link1/foo/bar/link1/foo").isSameFileAs("/foo");
}
@Test
public void testSecureDirectoryStream() throws IOException {
Files.createDirectories(path("/foo/bar"));
Files.createFile(path("/foo/a"));
Files.createFile(path("/foo/b"));
Files.createSymbolicLink(path("/foo/barLink"), path("bar"));
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path("/foo"))) {
if (!(stream instanceof SecureDirectoryStream)) {
fail("should be a secure directory stream");
}
SecureDirectoryStream<Path> secureStream = (SecureDirectoryStream<Path>) stream;
ASSERT.that(ImmutableList.copyOf(secureStream)).isEqualTo(
ImmutableList.of(
path("/foo/a"), path("/foo/b"), path("/foo/bar"), path("/foo/barLink")));
secureStream.deleteFile(path("b"));
assertThat("/foo/b").doesNotExist();
secureStream.newByteChannel(path("b"), ImmutableSet.of(CREATE_NEW)).close();
assertThat("/foo/b").isRegularFile();
Files.createDirectory(path("/baz"));
Files.move(path("/foo"), path("/baz/stuff"));
assertThat(path("/foo")).doesNotExist();
assertThat("/baz/stuff").hasChildren("a", "b", "bar", "barLink");
secureStream.deleteFile(path("b"));
assertThat("/baz/stuff/b").doesNotExist();
assertThat("/baz/stuff").hasChildren("a", "bar", "barLink");
ASSERT.that(secureStream.getFileAttributeView(BasicFileAttributeView.class)
.readAttributes()
.isDirectory()).isTrue();
ASSERT.that(secureStream.getFileAttributeView(path("a"), BasicFileAttributeView.class)
.readAttributes()
.isRegularFile()).isTrue();
try {
secureStream.deleteFile(path("bar"));
fail();
} catch (FileSystemException expected) {
ASSERT.that(expected.getFile()).isEqualTo("bar");
}
try {
secureStream.deleteDirectory(path("a"));
fail();
} catch (FileSystemException expected) {
ASSERT.that(expected.getFile()).isEqualTo("a");
}
try (SecureDirectoryStream<Path> barStream = secureStream.newDirectoryStream(path("bar"))) {
barStream.newByteChannel(path("stuff"), ImmutableSet.of(CREATE_NEW)).close();
ASSERT.that(barStream.getFileAttributeView(path("stuff"), BasicFileAttributeView.class)
.readAttributes()
.isRegularFile()).isTrue();
ASSERT.that(secureStream.getFileAttributeView(
path("bar/stuff"), BasicFileAttributeView.class)
.readAttributes()
.isRegularFile()).isTrue();
}
try (SecureDirectoryStream<Path> barLinkStream = secureStream
.newDirectoryStream(path("barLink"))) {
ASSERT.that(barLinkStream.getFileAttributeView(
path("stuff"), BasicFileAttributeView.class)
.readAttributes()
.isRegularFile()).isTrue();
ASSERT.that(barLinkStream.getFileAttributeView(
path(".."), BasicFileAttributeView.class)
.readAttributes()
.isDirectory()).isTrue();
}
try {
secureStream.newDirectoryStream(path("barLink"), NOFOLLOW_LINKS);
fail();
} catch (NotDirectoryException expected) {
ASSERT.that(expected.getFile()).isEqualTo("barLink");
}
try (SecureDirectoryStream<Path> barStream = secureStream.newDirectoryStream(path("bar"))) {
secureStream.move(path("a"), barStream, path("moved"));
assertThat(path("/baz/stuff/a")).doesNotExist();
assertThat(path("/baz/stuff/bar/moved")).isRegularFile();
ASSERT.that(barStream.getFileAttributeView(path("moved"), BasicFileAttributeView.class)
.readAttributes()
.isRegularFile()).isTrue();
}
}
}
@Test
public void testSecureDirectoryStreamBasedOnRelativePath() throws IOException {
Files.createDirectories(path("foo"));
Files.createFile(path("foo/a"));
Files.createFile(path("foo/b"));
Files.createDirectory(path("foo/c"));
Files.createFile(path("foo/c/d"));
Files.createFile(path("foo/c/e"));
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path("foo"))) {
SecureDirectoryStream<Path> secureStream = (SecureDirectoryStream<Path>) stream;
ASSERT.that(ImmutableList.copyOf(secureStream)).has()
.exactly(path("foo/a"), path("foo/b"), path("foo/c"));
try (DirectoryStream<Path> stream2 = secureStream.newDirectoryStream(path("c"))) {
ASSERT.that(ImmutableList.copyOf(stream2)).has()
.exactly(path("foo/c/d"), path("foo/c/e"));
}
}
}
@Test
public void testDirectoryAccessAndModifiedTimeUpdates() throws IOException {
Files.createDirectories(path("/foo/bar"));
FileTimeTester tester = new FileTimeTester(path("/foo/bar"));
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
// TODO(cgdecker): Use a Clock for file times so I can test this reliably without sleeping
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
Files.createFile(path("/foo/bar/baz.txt"));
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeChanged();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
// access time is updated by reading the full contents of the directory
// not just by doing a lookup in it
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path("/foo/bar"))) {
// iterate the stream, forcing the directory to actually be read
Iterators.advance(stream.iterator(), Integer.MAX_VALUE);
}
tester.assertAccessTimeChanged();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
Files.move(path("/foo/bar/baz.txt"), path("/foo/bar/baz2.txt"));
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeChanged();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
Files.delete(path("/foo/bar/baz2.txt"));
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeChanged();
}
@Test
public void testRegularFileAccessAndModifiedTimeUpdates() throws IOException {
Path foo = path("foo");
Files.createFile(foo);
FileTimeTester tester = new FileTimeTester(foo);
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
try (FileChannel channel = FileChannel.open(foo, READ)) {
// opening READ channel does not change times
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
channel.read(ByteBuffer.allocate(100));
// read call on channel does
tester.assertAccessTimeChanged();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
channel.read(ByteBuffer.allocate(100));
tester.assertAccessTimeChanged();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
try {
channel.write(ByteBuffer.wrap(new byte[]{0, 1, 2, 3}));
} catch (NonWritableChannelException ignore) {
}
// failed write on non-readable channel does not change times
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
}
// closing channel does not change times
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
try (FileChannel channel = FileChannel.open(foo, WRITE)) {
// opening WRITE channel does not change times
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
channel.write(ByteBuffer.wrap(new byte[]{0, 1, 2, 3}));
// write call on channel does
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeChanged();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
channel.write(ByteBuffer.wrap(new byte[]{4, 5, 6, 7}));
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeChanged();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
try {
channel.read(ByteBuffer.allocate(100));
} catch (NonReadableChannelException ignore) {
}
// failed read on non-readable channel does not change times
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
Uninterruptibles.sleepUninterruptibly(1, MILLISECONDS);
}
// closing channel does not change times
tester.assertAccessTimeDidNotChange();
tester.assertModifiedTimeDidNotChange();
}
// helpers
private Path path(String first, String... more) {
return fs.getPath(first, more);
}
private Object getFileKey(String path, LinkOption... options) throws IOException {
return Files.getAttribute(path(path), "fileKey", options);
}
private PathSubject assertThat(String path, LinkOption... options) {
return assertThat(path(path), options);
}
private static PathSubject assertThat(Path path, LinkOption... options) {
PathSubject subject = ASSERT.about(paths()).that(path);
if (options.length != 0) {
subject = subject.noFollowLinks();
}
return subject;
}
/**
* Tester for testing changes in file times.
*/
private static final class FileTimeTester {
private final Path path;
private FileTime accessTime;
private FileTime modifiedTime;
private FileTimeTester(Path path) throws IOException {
this.path = path;
BasicFileAttributes attrs = attrs();
accessTime = attrs.lastAccessTime();
modifiedTime = attrs.lastModifiedTime();
}
private BasicFileAttributes attrs() throws IOException {
return Files.readAttributes(path, BasicFileAttributes.class);
}
public void assertAccessTimeChanged() throws IOException {
FileTime t = attrs().lastAccessTime();
ASSERT.that(t).isNotEqualTo(accessTime);
accessTime = t;
}
public void assertAccessTimeDidNotChange() throws IOException {
FileTime t = attrs().lastAccessTime();
ASSERT.that(t).isEqualTo(accessTime);
}
public void assertModifiedTimeChanged() throws IOException {
FileTime t = attrs().lastModifiedTime();
ASSERT.that(t).isNotEqualTo(modifiedTime);
modifiedTime = t;
}
public void assertModifiedTimeDidNotChange() throws IOException {
FileTime t = attrs().lastModifiedTime();
ASSERT.that(t).isEqualTo(modifiedTime);
}
}
}