aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java
diff options
context:
space:
mode:
Diffstat (limited to 'jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java')
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java2401
1 files changed, 2401 insertions, 0 deletions
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java
new file mode 100644
index 0000000..a839d6a
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java
@@ -0,0 +1,2401 @@
+/*
+ * 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.common.jimfs;
+
+import static com.google.common.jimfs.TestUtils.bytes;
+import static com.google.common.jimfs.TestUtils.permutations;
+import static com.google.common.jimfs.TestUtils.preFilledBytes;
+import static com.google.common.primitives.Bytes.concat;
+import static com.google.common.truth.Truth.assertThat;
+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.COPY_ATTRIBUTES;
+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 com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Ordering;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.CharStreams;
+import com.google.common.primitives.Bytes;
+import com.google.common.util.concurrent.Uninterruptibles;
+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.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.FileChannel;
+import java.nio.channels.NonReadableChannelException;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.ClosedDirectoryStreamException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileStore;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.NotLinkException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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.Arrays;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.regex.PatternSyntaxException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests an in-memory file system through the public APIs in {@link Files}, etc. This also acts as
+ * the tests for {@code FileSystemView}, as each public API method is (mostly) implemented by a
+ * method in {@code FileSystemView}.
+ *
+ * <p>These tests uses a Unix-like file system, but most of what they test applies to any file
+ * system configuration.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsUnixLikeFileSystemTest extends AbstractJimfsIntegrationTest {
+
+ private static final Configuration UNIX_CONFIGURATION =
+ Configuration.unix().toBuilder()
+ .setAttributeViews("basic", "owner", "posix", "unix")
+ .setMaxSize(1024 * 1024 * 1024) // 1 GB
+ .setMaxCacheSize(256 * 1024 * 1024) // 256 MB
+ .build();
+
+ @Override
+ protected FileSystem createFileSystem() {
+ return Jimfs.newFileSystem("unix", UNIX_CONFIGURATION);
+ }
+
+ @Test
+ public void testFileSystem() {
+ assertThat(fs.getSeparator()).isEqualTo("/");
+ assertThat(fs.getRootDirectories())
+ .containsExactlyElementsIn(ImmutableSet.of(path("/")))
+ .inOrder();
+ assertThat(fs.isOpen()).isTrue();
+ assertThat(fs.isReadOnly()).isFalse();
+ assertThat(fs.supportedFileAttributeViews()).containsExactly("basic", "owner", "posix", "unix");
+ assertThat(fs.provider()).isInstanceOf(JimfsFileSystemProvider.class);
+ }
+
+ @Test
+ public void testFileStore() throws IOException {
+ FileStore fileStore = Iterables.getOnlyElement(fs.getFileStores());
+ assertThat(fileStore.name()).isEqualTo("jimfs");
+ assertThat(fileStore.type()).isEqualTo("jimfs");
+ assertThat(fileStore.isReadOnly()).isFalse();
+
+ long totalSpace = 1024 * 1024 * 1024; // 1 GB
+ assertThat(fileStore.getTotalSpace()).isEqualTo(totalSpace);
+ assertThat(fileStore.getUnallocatedSpace()).isEqualTo(totalSpace);
+ assertThat(fileStore.getUsableSpace()).isEqualTo(totalSpace);
+
+ Files.write(fs.getPath("/foo"), new byte[10000]);
+
+ assertThat(fileStore.getTotalSpace()).isEqualTo(totalSpace);
+
+ // We wrote 10000 bytes, but since the file system allocates fixed size blocks, more than 10k
+ // bytes may have been allocated. As such, the unallocated space after the write can be at most
+ // maxUnallocatedSpace.
+ assertThat(fileStore.getUnallocatedSpace() <= totalSpace - 10000).isTrue();
+
+ // Usable space is at most unallocated space. (In this case, it's currently exactly unallocated
+ // space, but that's not required.)
+ assertThat(fileStore.getUsableSpace() <= fileStore.getUnallocatedSpace()).isTrue();
+
+ Files.delete(fs.getPath("/foo"));
+ assertThat(fileStore.getTotalSpace()).isEqualTo(totalSpace);
+ assertThat(fileStore.getUnallocatedSpace()).isEqualTo(totalSpace);
+ assertThat(fileStore.getUsableSpace()).isEqualTo(totalSpace);
+ }
+
+ @Test
+ public void testPaths() {
+ assertThatPath("/").isAbsolute().and().hasRootComponent("/").and().hasNoNameComponents();
+ assertThatPath("foo").isRelative().and().hasNameComponents("foo");
+ assertThatPath("foo/bar").isRelative().and().hasNameComponents("foo", "bar");
+ assertThatPath("/foo/bar/baz")
+ .isAbsolute()
+ .and()
+ .hasRootComponent("/")
+ .and()
+ .hasNameComponents("foo", "bar", "baz");
+ }
+
+ @Test
+ public void testPaths_equalityIsCaseSensitive() {
+ assertThatPath("foo").isNotEqualTo(path("FOO"));
+ }
+
+ @Test
+ public void testPaths_areSortedCaseSensitive() {
+ Path p1 = path("a");
+ Path p2 = path("B");
+ Path p3 = path("c");
+ Path p4 = path("D");
+
+ assertThat(Ordering.natural().immutableSortedCopy(Arrays.asList(p3, p4, p1, p2)))
+ .isEqualTo(ImmutableList.of(p2, p4, p1, p3));
+
+ // would be p1, p2, p3, p4 if sorting were case insensitive
+ }
+
+ @Test
+ public void testPaths_resolve() {
+ assertThatPath(path("/").resolve("foo/bar"))
+ .isAbsolute()
+ .and()
+ .hasRootComponent("/")
+ .and()
+ .hasNameComponents("foo", "bar");
+ assertThatPath(path("foo/bar").resolveSibling("baz"))
+ .isRelative()
+ .and()
+ .hasNameComponents("foo", "baz");
+ assertThatPath(path("foo/bar").resolve("/one/two"))
+ .isAbsolute()
+ .and()
+ .hasRootComponent("/")
+ .and()
+ .hasNameComponents("one", "two");
+ }
+
+ @Test
+ public void testPaths_normalize() {
+ assertThatPath(path("foo/bar/..").normalize()).isRelative().and().hasNameComponents("foo");
+ assertThatPath(path("foo/./bar/../baz/test/./../stuff").normalize())
+ .isRelative()
+ .and()
+ .hasNameComponents("foo", "baz", "stuff");
+ assertThatPath(path("../../foo/./bar").normalize())
+ .isRelative()
+ .and()
+ .hasNameComponents("..", "..", "foo", "bar");
+ assertThatPath(path("foo/../../bar").normalize())
+ .isRelative()
+ .and()
+ .hasNameComponents("..", "bar");
+ assertThatPath(path(".././..").normalize()).isRelative().and().hasNameComponents("..", "..");
+ }
+
+ @Test
+ public void testPaths_relativize() {
+ assertThatPath(path("/foo/bar").relativize(path("/foo/bar/baz")))
+ .isRelative()
+ .and()
+ .hasNameComponents("baz");
+ assertThatPath(path("/foo/bar/baz").relativize(path("/foo/bar")))
+ .isRelative()
+ .and()
+ .hasNameComponents("..");
+ assertThatPath(path("/foo/bar/baz").relativize(path("/foo/baz/bar")))
+ .isRelative()
+ .and()
+ .hasNameComponents("..", "..", "baz", "bar");
+ assertThatPath(path("foo/bar").relativize(path("foo")))
+ .isRelative()
+ .and()
+ .hasNameComponents("..");
+ assertThatPath(path("foo").relativize(path("foo/bar")))
+ .isRelative()
+ .and()
+ .hasNameComponents("bar");
+
+ try {
+ Path unused = path("/foo/bar").relativize(path("bar"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ Path unused = path("bar").relativize(path("/foo/bar"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testPaths_startsWith_endsWith() {
+ assertThat(path("/foo/bar").startsWith("/")).isTrue();
+ assertThat(path("/foo/bar").startsWith("/foo")).isTrue();
+ assertThat(path("/foo/bar").startsWith("/foo/bar")).isTrue();
+ assertThat(path("/foo/bar").endsWith("bar")).isTrue();
+ assertThat(path("/foo/bar").endsWith("foo/bar")).isTrue();
+ assertThat(path("/foo/bar").endsWith("/foo/bar")).isTrue();
+ assertThat(path("/foo/bar").endsWith("/foo")).isFalse();
+ assertThat(path("/foo/bar").startsWith("foo/bar")).isFalse();
+ }
+
+ @Test
+ public void testPaths_toAbsolutePath() {
+ assertThatPath(path("/foo/bar").toAbsolutePath())
+ .isAbsolute()
+ .and()
+ .hasRootComponent("/")
+ .and()
+ .hasNameComponents("foo", "bar")
+ .and()
+ .isEqualTo(path("/foo/bar"));
+
+ assertThatPath(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("/"));
+
+ assertThatPath(path("/link/foo/bar").toRealPath()).isEqualTo(path("/foo/bar"));
+
+ assertThatPath(path("").toRealPath()).isEqualTo(path("/work"));
+ assertThatPath(path(".").toRealPath()).isEqualTo(path("/work"));
+ assertThatPath(path("..").toRealPath()).isEqualTo(path("/"));
+ assertThatPath(path("../..").toRealPath()).isEqualTo(path("/"));
+ assertThatPath(path("./.././..").toRealPath()).isEqualTo(path("/"));
+ assertThatPath(path("./.././../.").toRealPath()).isEqualTo(path("/"));
+ }
+
+ @Test
+ public void testPaths_toUri() {
+ assertThat(path("/").toUri()).isEqualTo(URI.create("jimfs://unix/"));
+ assertThat(path("/foo").toUri()).isEqualTo(URI.create("jimfs://unix/foo"));
+ assertThat(path("/foo/bar").toUri()).isEqualTo(URI.create("jimfs://unix/foo/bar"));
+ assertThat(path("foo").toUri()).isEqualTo(URI.create("jimfs://unix/work/foo"));
+ assertThat(path("foo/bar").toUri()).isEqualTo(URI.create("jimfs://unix/work/foo/bar"));
+ assertThat(path("").toUri()).isEqualTo(URI.create("jimfs://unix/work/"));
+ assertThat(path("./../.").toUri()).isEqualTo(URI.create("jimfs://unix/work/./.././"));
+ }
+
+ @Test
+ public void testPaths_getFromUri() {
+ assertThatPath(Paths.get(URI.create("jimfs://unix/"))).isEqualTo(path("/"));
+ assertThatPath(Paths.get(URI.create("jimfs://unix/foo"))).isEqualTo(path("/foo"));
+ assertThatPath(Paths.get(URI.create("jimfs://unix/foo%20bar"))).isEqualTo(path("/foo bar"));
+ assertThatPath(Paths.get(URI.create("jimfs://unix/foo/./bar"))).isEqualTo(path("/foo/./bar"));
+ assertThatPath(Paths.get(URI.create("jimfs://unix/foo/bar/"))).isEqualTo(path("/foo/bar"));
+ }
+
+ @Test
+ public void testPathMatchers_regex() {
+ assertThatPath("bar").matches("regex:.*");
+ assertThatPath("bar").matches("regex:bar");
+ assertThatPath("bar").matches("regex:[a-z]+");
+ assertThatPath("/foo/bar").matches("regex:/.*");
+ assertThatPath("/foo/bar").matches("regex:/.*/bar");
+ }
+
+ @Test
+ public void testPathMatchers_glob() {
+ assertThatPath("bar").matches("glob:bar");
+ assertThatPath("bar").matches("glob:*");
+ assertThatPath("/foo").doesNotMatch("glob:*");
+ assertThatPath("/foo/bar").doesNotMatch("glob:*");
+ assertThatPath("/foo/bar").matches("glob:**");
+ assertThatPath("/foo/bar").matches("glob:/**");
+ assertThatPath("foo/bar").doesNotMatch("glob:/**");
+ assertThatPath("/foo/bar/baz/stuff").matches("glob:/foo/**");
+ assertThatPath("/foo/bar/baz/stuff").matches("glob:/**/stuff");
+ assertThatPath("/foo").matches("glob:/[a-z]*");
+ assertThatPath("/Foo").doesNotMatch("glob:/[a-z]*");
+ assertThatPath("/foo/bar/baz/Stuff.java").matches("glob:**/*.java");
+ assertThatPath("/foo/bar/baz/Stuff.java").matches("glob:**/*.{java,class}");
+ assertThatPath("/foo/bar/baz/Stuff.class").matches("glob:**/*.{java,class}");
+ assertThatPath("/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) {
+ assertThat(expected.getMessage()).contains("syntax");
+ }
+ }
+
+ @Test
+ public void testNewFileSystem_hasRootAndWorkingDirectory() throws IOException {
+ assertThatPath("/").hasChildren("work");
+ assertThatPath("/work").hasNoChildren();
+ }
+
+ @Test
+ public void testCreateDirectory_absolute() throws IOException {
+ Files.createDirectory(path("/test"));
+
+ assertThatPath("/test").exists();
+ assertThatPath("/").hasChildren("test", "work");
+
+ Files.createDirectory(path("/foo"));
+ Files.createDirectory(path("/foo/bar"));
+
+ assertThatPath("/foo/bar").exists();
+ assertThatPath("/foo").hasChildren("bar");
+ }
+
+ @Test
+ public void testCreateFile_absolute() throws IOException {
+ Files.createFile(path("/test.txt"));
+
+ assertThatPath("/test.txt").isRegularFile();
+ assertThatPath("/").hasChildren("test.txt", "work");
+
+ Files.createDirectory(path("/foo"));
+ Files.createFile(path("/foo/test.txt"));
+
+ assertThatPath("/foo/test.txt").isRegularFile();
+ assertThatPath("/foo").hasChildren("test.txt");
+ }
+
+ @Test
+ public void testCreateSymbolicLink_absolute() throws IOException {
+ Files.createSymbolicLink(path("/link.txt"), path("test.txt"));
+
+ assertThatPath("/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt");
+ assertThatPath("/").hasChildren("link.txt", "work");
+
+ Files.createDirectory(path("/foo"));
+ Files.createSymbolicLink(path("/foo/link.txt"), path("test.txt"));
+
+ assertThatPath("/foo/link.txt").noFollowLinks().isSymbolicLink().withTarget("test.txt");
+ assertThatPath("/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
+ assertThatPath("/link.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("/").hasChildren("link.txt", "test.txt", "work");
+
+ Files.createDirectory(path("/foo"));
+ Files.createLink(path("/foo/link.txt"), path("/test.txt"));
+
+ assertThatPath("/foo/link.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("/foo").hasChildren("link.txt");
+ }
+
+ @Test
+ public void testCreateDirectory_relative() throws IOException {
+ Files.createDirectory(path("test"));
+
+ assertThatPath("/work/test", NOFOLLOW_LINKS).isDirectory();
+ assertThatPath("test", NOFOLLOW_LINKS).isDirectory();
+ assertThatPath("/work").hasChildren("test");
+ assertThatPath("test").isSameFileAs("/work/test");
+
+ Files.createDirectory(path("foo"));
+ Files.createDirectory(path("foo/bar"));
+
+ assertThatPath("/work/foo/bar", NOFOLLOW_LINKS).isDirectory();
+ assertThatPath("foo/bar", NOFOLLOW_LINKS).isDirectory();
+ assertThatPath("/work/foo").hasChildren("bar");
+ assertThatPath("foo").hasChildren("bar");
+ assertThatPath("foo/bar").isSameFileAs("/work/foo/bar");
+ }
+
+ @Test
+ public void testCreateFile_relative() throws IOException {
+ Files.createFile(path("test.txt"));
+
+ assertThatPath("/work/test.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("test.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("/work").hasChildren("test.txt");
+ assertThatPath("test.txt").isSameFileAs("/work/test.txt");
+
+ Files.createDirectory(path("foo"));
+ Files.createFile(path("foo/test.txt"));
+
+ assertThatPath("/work/foo/test.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("foo/test.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("/work/foo").hasChildren("test.txt");
+ assertThatPath("foo").hasChildren("test.txt");
+ assertThatPath("foo/test.txt").isSameFileAs("/work/foo/test.txt");
+ }
+
+ @Test
+ public void testCreateSymbolicLink_relative() throws IOException {
+ Files.createSymbolicLink(path("link.txt"), path("test.txt"));
+
+ assertThatPath("/work/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt");
+ assertThatPath("link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt");
+ assertThatPath("/work").hasChildren("link.txt");
+
+ Files.createDirectory(path("foo"));
+ Files.createSymbolicLink(path("foo/link.txt"), path("test.txt"));
+
+ assertThatPath("/work/foo/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt");
+ assertThatPath("foo/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("test.txt");
+ assertThatPath("/work/foo").hasChildren("link.txt");
+ assertThatPath("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
+ assertThatPath("/work/link.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("link.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("/work").hasChildren("link.txt", "test.txt");
+
+ Files.createDirectory(path("foo"));
+ Files.createLink(path("foo/link.txt"), path("test.txt"));
+
+ assertThatPath("/work/foo/link.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("foo/link.txt", NOFOLLOW_LINKS).isRegularFile();
+ assertThatPath("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) {
+ assertThat(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) {
+ assertThat(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) {
+ assertThat(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) {
+ assertThat(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) {
+ assertThat(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) {
+ assertThat(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);
+
+ assertThatPath("/normal").attribute("posix:permissions").isNot(permissions);
+ assertThatPath("/foo").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)));
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ assertThatPath("/foo").doesNotExist();
+
+ try {
+ Files.createFile(path("/foo"), new BasicFileAttribute<>("basic:noSuchAttribute", "foo"));
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ assertThatPath("/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(WRITE, CREATE), permissions).close();
+
+ assertThatPath("/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(WRITE, CREATE), permissions).close();
+
+ assertThatPath("/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);
+
+ assertThatPath("/foo")
+ .isDirectory()
+ .and()
+ .attribute("posix:permissions")
+ .is(permissions.value());
+
+ Files.createDirectory(path("/normal"));
+
+ assertThatPath("/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);
+
+ assertThatPath("/foo", NOFOLLOW_LINKS)
+ .isSymbolicLink()
+ .and()
+ .attribute("posix:permissions")
+ .is(permissions.value());
+
+ Files.createSymbolicLink(path("/normal"), path("bar"));
+
+ assertThatPath("/normal", NOFOLLOW_LINKS)
+ .isSymbolicLink()
+ .and()
+ .attribute("posix:permissions")
+ .isNot(permissions.value());
+ }
+
+ @Test
+ public void testCreateDirectories() throws IOException {
+ Files.createDirectories(path("/foo/bar/baz"));
+
+ assertThatPath("/foo").isDirectory();
+ assertThatPath("/foo/bar").isDirectory();
+ assertThatPath("/foo/bar/baz").isDirectory();
+
+ Files.createDirectories(path("/foo/asdf/jkl"));
+
+ assertThatPath("/foo/asdf").isDirectory();
+ assertThatPath("/foo/asdf/jkl").isDirectory();
+
+ Files.createDirectories(path("bar/baz"));
+
+ assertThatPath("bar/baz").isDirectory();
+ assertThatPath("/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"));
+
+ assertThatPath("/foo").hasLinkCount(2);
+ }
+
+ @Test
+ public void testDirectories_creatingDirectoryAddsOneLinkToParent() throws IOException {
+ // from the .. direntry
+
+ Files.createDirectory(path("/foo"));
+ Files.createDirectory(path("/foo/bar"));
+
+ assertThatPath("/foo").hasLinkCount(3);
+
+ Files.createDirectory(path("/foo/baz"));
+
+ assertThatPath("/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"));
+
+ assertThatPath("/foo").hasLinkCount(2);
+ }
+
+ @Test
+ public void testSize_forNewFile_isZero() throws IOException {
+ Files.createFile(path("/test"));
+
+ assertThatPath("/test").hasSize(0);
+ }
+
+ @Test
+ public void testRead_forNewFile_isEmpty() throws IOException {
+ Files.createFile(path("/test"));
+
+ assertThatPath("/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});
+
+ assertThatPath("/test").hasSize(4);
+ }
+
+ @Test
+ public void testRead_forFileAfterWrite_isBytesWritten() throws IOException {
+ byte[] bytes = {0, 1, 2, 3};
+ Files.write(path("/test"), bytes);
+
+ assertThatPath("/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
+ assertThatPath("/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
+ assertThatPath("/test").containsBytes(4, 5, 2, 3); // did not truncate or append, so overwrote
+
+ Files.write(test, bytes, WRITE, CREATE, TRUNCATE_EXISTING); // default options
+ assertThatPath("/test").containsBytes(bytes);
+
+ Files.write(test, bytes, WRITE, APPEND);
+ assertThatPath("/test").containsBytes(0, 1, 2, 3, 0, 1, 2, 3);
+
+ Files.write(test, bytes, WRITE, CREATE, TRUNCATE_EXISTING, APPEND, SPARSE, DSYNC, SYNC);
+ assertThatPath("/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 testOpenFile_withReadAndTruncateExisting_doesNotTruncateFile() throws IOException {
+ byte[] bytes = bytes(1, 2, 3, 4);
+ Files.write(path("/test"), bytes);
+
+ try (FileChannel channel = FileChannel.open(path("/test"), READ, TRUNCATE_EXISTING)) {
+ // TRUNCATE_EXISTING ignored when opening for read
+ byte[] readBytes = new byte[4];
+ channel.read(ByteBuffer.wrap(readBytes));
+
+ assertThat(Bytes.asList(readBytes)).isEqualTo(Bytes.asList(bytes));
+ }
+ }
+
+ @Test
+ public void testRead_forFileAfterWriteLines_isLinesWritten() throws IOException {
+ Files.write(path("/test.txt"), ImmutableList.of("hello", "world"), UTF_8);
+
+ assertThatPath("/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
+ assertThatPath(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 (System.getProperty("line.separator").length() == 2) {
+ // on Windows, an extra character is overwritten by the \r\n line separator
+ assertThatPath(test).containsLines("foo", "", "world");
+ } else {
+ assertThatPath(test).containsLines("foo", "o", "world");
+ }
+
+ Files.write(test, lines, UTF_8, WRITE, CREATE, TRUNCATE_EXISTING); // default options
+ assertThatPath(test).containsLines(lines);
+
+ Files.write(test, lines, UTF_8, WRITE, APPEND);
+ assertThatPath(test).containsLines("hello", "world", "hello", "world");
+
+ Files.write(test, lines, UTF_8, WRITE, CREATE, TRUNCATE_EXISTING, APPEND, SPARSE, DSYNC, SYNC);
+ assertThatPath(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);
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("/foo");
+ assertThat(expected.getMessage()).contains("regular file");
+ }
+
+ try {
+ // CREATE mode
+ Files.write(path("/foo"), preFilledBytes(10));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("/foo");
+ assertThat(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"));
+ assertThatPath("/test").isRegularFile();
+
+ Files.delete(path("/test"));
+ assertThatPath("/test").doesNotExist();
+
+ Files.createFile(path("/test"));
+
+ assertTrue(Files.deleteIfExists(path("/test")));
+ assertThatPath("/test").doesNotExist();
+ }
+
+ @Test
+ public void testDelete_file_whenOpenReferencesRemain() throws IOException {
+ // the open streams should continue to function normally despite the deletion
+
+ Path foo = path("/foo");
+ byte[] bytes = preFilledBytes(100);
+ Files.write(foo, bytes);
+
+ InputStream in = Files.newInputStream(foo);
+ OutputStream out = Files.newOutputStream(foo, APPEND);
+ FileChannel channel = FileChannel.open(foo, READ, WRITE);
+
+ assertThat(channel.size()).isEqualTo(100L);
+
+ Files.delete(foo);
+ assertThatPath("/foo").doesNotExist();
+
+ assertThat(channel.size()).isEqualTo(100L);
+
+ ByteBuffer buf = ByteBuffer.allocate(100);
+ while (buf.hasRemaining()) {
+ channel.read(buf);
+ }
+
+ assertArrayEquals(bytes, buf.array());
+
+ byte[] moreBytes = {1, 2, 3, 4, 5};
+ out.write(moreBytes);
+
+ assertThat(channel.size()).isEqualTo(105L);
+ buf.clear();
+ assertThat(channel.read(buf)).isEqualTo(5);
+
+ buf.flip();
+ byte[] b = new byte[5];
+ buf.get(b);
+ assertArrayEquals(moreBytes, b);
+
+ byte[] allBytes = new byte[105];
+ int off = 0;
+ int read;
+ while ((read = in.read(allBytes, off, allBytes.length - off)) != -1) {
+ off += read;
+ }
+ assertArrayEquals(concat(bytes, moreBytes), allBytes);
+
+ channel.close();
+ out.close();
+ in.close();
+ }
+
+ @Test
+ public void testDelete_directory() throws IOException {
+ Files.createDirectories(path("/foo/bar"));
+ assertThatPath("/foo").isDirectory();
+ assertThatPath("/foo/bar").isDirectory();
+
+ Files.delete(path("/foo/bar"));
+ assertThatPath("/foo/bar").doesNotExist();
+
+ assertTrue(Files.deleteIfExists(path("/foo")));
+ assertThatPath("/foo").doesNotExist();
+ }
+
+ @Test
+ public void testDelete_pathPermutations() throws IOException {
+ Path bar = path("/work/foo/bar");
+ Files.createDirectories(bar);
+ for (Path path : permutations(bar)) {
+ Files.createDirectories(bar);
+ assertThatPath(path).isSameFileAs(bar);
+ Files.delete(path);
+ assertThatPath(bar).doesNotExist();
+ assertThatPath(path).doesNotExist();
+ }
+
+ Path baz = path("/test/baz");
+ Files.createDirectories(baz);
+ Path hello = baz.resolve("hello.txt");
+ for (Path path : permutations(hello)) {
+ Files.createFile(hello);
+ assertThatPath(path).isSameFileAs(hello);
+ Files.delete(path);
+ assertThatPath(hello).doesNotExist();
+ assertThatPath(path).doesNotExist();
+ }
+ }
+
+ @Test
+ public void testDelete_directory_cantDeleteNonEmptyDirectory() throws IOException {
+ Files.createDirectories(path("/foo/bar"));
+
+ try {
+ Files.delete(path("/foo"));
+ fail();
+ } catch (DirectoryNotEmptyException expected) {
+ assertThat(expected.getFile()).isEqualTo("/foo");
+ }
+
+ try {
+ Files.deleteIfExists(path("/foo"));
+ fail();
+ } catch (DirectoryNotEmptyException expected) {
+ assertThat(expected.getFile()).isEqualTo("/foo");
+ }
+ }
+
+ @Test
+ public void testDelete_directory_canDeleteWorkingDirectoryByAbsolutePath() throws IOException {
+ assertThatPath("/work").exists();
+ assertThatPath("").exists();
+ assertThatPath(".").exists();
+
+ Files.delete(path("/work"));
+
+ assertThatPath("/work").doesNotExist();
+ assertThatPath("").exists();
+ assertThatPath(".").exists();
+ }
+
+ @Test
+ public void testDelete_directory_cantDeleteWorkingDirectoryByRelativePath() throws IOException {
+ try {
+ Files.delete(path(""));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("");
+ }
+
+ try {
+ Files.delete(path("."));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo(".");
+ }
+
+ try {
+ Files.delete(path("../../work"));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("../../work");
+ }
+
+ try {
+ Files.delete(path("./../work/.././../work/."));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("./../work/.././../work/.");
+ }
+ }
+
+ @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
+ Files.delete(path("/work"));
+
+ try {
+ Files.delete(path("/"));
+ fail();
+ } catch (IOException expected) {
+ assertThat(expected.getMessage()).contains("root");
+ }
+
+ Files.createDirectories(path("/foo/bar"));
+
+ try {
+ Files.delete(path("/foo/bar/../.."));
+ fail();
+ } catch (IOException expected) {
+ assertThat(expected.getMessage()).contains("root");
+ }
+
+ try {
+ Files.delete(path("/foo/./../foo/bar/./../bar/.././../../.."));
+ fail();
+ } catch (IOException expected) {
+ assertThat(expected.getMessage()).contains("root");
+ }
+ }
+
+ @Test
+ public void testSymbolicLinks() throws IOException {
+ Files.createSymbolicLink(path("/link.txt"), path("/file.txt"));
+ assertThatPath("/link.txt", NOFOLLOW_LINKS).isSymbolicLink().withTarget("/file.txt");
+ assertThatPath("/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"));
+ assertThatPath("/link.txt").isRegularFile(); // following the link; target does exist
+ assertThatPath("/link.txt").containsNoBytes();
+
+ Files.createSymbolicLink(path("/foo"), path("/bar/baz"));
+ assertThatPath("/foo", NOFOLLOW_LINKS).isSymbolicLink().withTarget("/bar/baz");
+ assertThatPath("/foo").doesNotExist(); // following the link; target doesn't exist
+
+ Files.createDirectories(path("/bar/baz"));
+ assertThatPath("/foo").isDirectory(); // following the link; target does exist
+
+ Files.createFile(path("/bar/baz/test.txt"));
+ assertThatPath("/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) {
+ assertThat(expected.getMessage()).contains("symbolic link");
+ }
+
+ try {
+ Files.write(path("/foo/bar"), preFilledBytes(10));
+ fail();
+ } catch (IOException expected) {
+ assertThat(expected.getMessage()).contains("symbolic link");
+ }
+ }
+
+ @Test
+ public void testSymbolicLinks_lookupOfAbsoluteSymlinkPathFromRelativePath() throws IOException {
+ // relative path lookups are in the FileSystemView for the working directory
+ // this tests that when an absolute path is encountered, the lookup handles it correctly
+
+ 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"));
+
+ assertThatPath("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
+ assertThatPath("/file.txt").hasLinkCount(1);
+
+ Files.createLink(path("/link.txt"), path("/file.txt"));
+
+ assertThatPath("/link.txt").isSameFileAs("/file.txt");
+
+ assertThatPath("/file.txt").hasLinkCount(2);
+ assertThatPath("/link.txt").hasLinkCount(2);
+
+ assertThatPath("/file.txt").containsNoBytes();
+ assertThatPath("/link.txt").containsNoBytes();
+
+ byte[] bytes = {0, 1, 2, 3};
+ Files.write(path("/file.txt"), bytes);
+
+ assertThatPath("/file.txt").containsBytes(bytes);
+ assertThatPath("/link.txt").containsBytes(bytes);
+
+ Files.write(path("/link.txt"), bytes, APPEND);
+
+ assertThatPath("/file.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3);
+ assertThatPath("/link.txt").containsBytes(0, 1, 2, 3, 0, 1, 2, 3);
+
+ Files.delete(path("/file.txt"));
+ assertThatPath("/link.txt").hasLinkCount(1);
+
+ assertThatPath("/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"));
+
+ assertThatPath("/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());
+ }
+
+ assertThatPath("/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 testOutputStream_withTruncateExistingAndNotWrite_truncatesFile() throws IOException {
+ // https://github.com/google/jimfs/pull/77
+ Path path = path("/test");
+ Files.write(path, new byte[] {1, 2, 3});
+ assertThatPath(path).containsBytes(1, 2, 3);
+
+ try (OutputStream out = Files.newOutputStream(path, CREATE, TRUNCATE_EXISTING)) {
+ out.write(new byte[] {1, 2});
+ }
+
+ assertThatPath(path).containsBytes(1, 2);
+ }
+
+ @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});
+ }
+
+ assertEquals(11, channel.position());
+ assertEquals(11, channel.size());
+
+ channel.write(UTF_8.encode("!"));
+
+ assertEquals(12, channel.position());
+ assertEquals(12, channel.size());
+ }
+
+ try (SeekableByteChannel channel = Files.newByteChannel(path("/test.txt"), READ)) {
+ assertEquals(0, channel.position());
+ assertEquals(12, channel.size());
+
+ 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 = Bytes.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"));
+ assertThatPath("/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);
+ assertThatPath("/test").containsBytes(bytes);
+
+ Files.copy(new ByteArrayInputStream(bytes), path("/foo"), REPLACE_EXISTING);
+ assertThatPath("/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);
+
+ assertThatPath("/bar").doesNotExist();
+ Files.copy(path("/foo"), path("/bar"));
+ assertThatPath("/bar").containsBytes(bytes);
+
+ byte[] moreBytes = preFilledBytes(2048);
+ Files.write(path("/baz"), moreBytes);
+
+ Files.copy(path("/baz"), path("/bar"), REPLACE_EXISTING);
+ assertThatPath("/bar").containsBytes(moreBytes);
+
+ try {
+ Files.copy(path("/none"), path("/bar"));
+ fail();
+ } catch (NoSuchFileException expected) {
+ assertEquals("/none", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void testCopy_withCopyAttributes() throws IOException {
+ Path foo = path("/foo");
+ Files.createFile(foo);
+
+ Files.getFileAttributeView(foo, BasicFileAttributeView.class)
+ .setTimes(FileTime.fromMillis(100), FileTime.fromMillis(1000), FileTime.fromMillis(10000));
+
+ assertThat(Files.getAttribute(foo, "lastModifiedTime")).isEqualTo(FileTime.fromMillis(100));
+
+ UserPrincipal zero = fs.getUserPrincipalLookupService().lookupPrincipalByName("zero");
+ Files.setAttribute(foo, "owner:owner", zero);
+
+ Path bar = path("/bar");
+ Files.copy(foo, bar, COPY_ATTRIBUTES);
+
+ BasicFileAttributes attributes = Files.readAttributes(bar, BasicFileAttributes.class);
+ assertThat(attributes.lastModifiedTime()).isEqualTo(FileTime.fromMillis(100));
+ assertThat(attributes.lastAccessTime()).isEqualTo(FileTime.fromMillis(1000));
+ assertThat(attributes.creationTime()).isEqualTo(FileTime.fromMillis(10000));
+ assertThat(Files.getAttribute(bar, "owner:owner")).isEqualTo(zero);
+
+ Path baz = path("/baz");
+ Files.copy(foo, baz);
+
+ // test that attributes are not copied when COPY_ATTRIBUTES is not specified
+ attributes = Files.readAttributes(baz, BasicFileAttributes.class);
+ assertThat(attributes.lastModifiedTime()).isNotEqualTo(FileTime.fromMillis(100));
+ assertThat(attributes.lastAccessTime()).isNotEqualTo(FileTime.fromMillis(1000));
+ assertThat(attributes.creationTime()).isNotEqualTo(FileTime.fromMillis(10000));
+ assertThat(Files.getAttribute(baz, "owner:owner")).isNotEqualTo(zero);
+ }
+
+ @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"));
+
+ assertThatPath("/bar").doesNotExist();
+ Files.copy(path("/foo"), path("/bar"));
+ assertThatPath("/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"));
+
+ assertThatPath("/bar").isRegularFile();
+
+ // overwrite regular file w/ directory
+ Files.copy(path("/test"), path("/bar"), REPLACE_EXISTING);
+
+ assertThatPath("/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);
+
+ assertThatPath("/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);
+ assertThatPath("/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"));
+ assertThatPath("/bar").hasNoChildren();
+ }
+
+ @Test
+ public void testCopy_symbolicLinkToPath() throws IOException {
+ byte[] bytes = preFilledBytes(128);
+ Files.write(path("/test"), bytes);
+ Files.createSymbolicLink(path("/link"), path("/test"));
+
+ assertThatPath("/bar").doesNotExist();
+ Files.copy(path("/link"), path("/bar"));
+ assertThatPath("/bar", NOFOLLOW_LINKS).containsBytes(bytes);
+
+ Files.delete(path("/bar"));
+
+ Files.copy(path("/link"), path("/bar"), NOFOLLOW_LINKS);
+ assertThatPath("/bar", NOFOLLOW_LINKS).isSymbolicLink().withTarget("/test");
+ assertThatPath("/bar").isRegularFile();
+ assertThatPath("/bar").containsBytes(bytes);
+
+ Files.delete(path("/test"));
+ assertThatPath("/bar", NOFOLLOW_LINKS).isSymbolicLink();
+ assertThatPath("/bar").doesNotExist();
+ }
+
+ @Test
+ public void testCopy_toDifferentFileSystem() throws IOException {
+ try (FileSystem fs2 = Jimfs.newFileSystem(UNIX_CONFIGURATION)) {
+ Path foo = fs.getPath("/foo");
+ byte[] bytes = {0, 1, 2, 3, 4};
+ Files.write(foo, bytes);
+
+ Path foo2 = fs2.getPath("/foo");
+ Files.copy(foo, foo2);
+
+ assertThatPath(foo).exists();
+ assertThatPath(foo2).exists().and().containsBytes(bytes);
+ }
+ }
+
+ @Test
+ public void testCopy_toDifferentFileSystem_copyAttributes() throws IOException {
+ try (FileSystem fs2 = Jimfs.newFileSystem(UNIX_CONFIGURATION)) {
+ Path foo = fs.getPath("/foo");
+ byte[] bytes = {0, 1, 2, 3, 4};
+ Files.write(foo, bytes);
+ Files.getFileAttributeView(foo, BasicFileAttributeView.class)
+ .setTimes(FileTime.fromMillis(0), FileTime.fromMillis(1), FileTime.fromMillis(2));
+
+ UserPrincipal owner = fs.getUserPrincipalLookupService().lookupPrincipalByName("foobar");
+ Files.setOwner(foo, owner);
+
+ assertThatPath(foo).attribute("owner:owner").is(owner);
+
+ Path foo2 = fs2.getPath("/foo");
+ Files.copy(foo, foo2, COPY_ATTRIBUTES);
+
+ assertThatPath(foo).exists();
+
+ // when copying with COPY_ATTRIBUTES to a different FileSystem, only basic attributes (that
+ // is, file times) can actually be copied
+ assertThatPath(foo2)
+ .exists()
+ .and()
+ .attribute("lastModifiedTime")
+ .is(FileTime.fromMillis(0))
+ .and()
+ .attribute("lastAccessTime")
+ .is(FileTime.fromMillis(1))
+ .and()
+ .attribute("creationTime")
+ .is(FileTime.fromMillis(2))
+ .and()
+ .attribute("owner:owner")
+ .isNot(owner)
+ .and()
+ .attribute("owner:owner")
+ .isNot(fs2.getUserPrincipalLookupService().lookupPrincipalByName("foobar"))
+ .and()
+ .containsBytes(bytes); // do this last; it updates the access time
+ }
+ }
+
+ @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"));
+ assertThatPath("/foo").doesNotExist();
+ assertThatPath("/bar").containsBytes(bytes).and().attribute("fileKey").is(fooKey);
+
+ Files.createDirectory(path("/foo"));
+ Files.move(path("/bar"), path("/foo/bar"));
+
+ assertThatPath("/bar").doesNotExist();
+ assertThatPath("/foo/bar").isRegularFile();
+
+ Files.move(path("/foo"), path("/baz"));
+ assertThatPath("/foo").doesNotExist();
+ assertThatPath("/baz").isDirectory();
+ assertThatPath("/baz/bar").isRegularFile();
+ }
+
+ @Test
+ public void testMove_movesSymbolicLinkNotTarget() throws IOException {
+ byte[] bytes = preFilledBytes(100);
+ Files.write(path("/foo.txt"), bytes);
+
+ Files.createSymbolicLink(path("/link"), path("foo.txt"));
+
+ Files.move(path("/link"), path("/link.txt"));
+
+ assertThatPath("/foo.txt").noFollowLinks().isRegularFile().and().containsBytes(bytes);
+
+ assertThatPath(path("/link")).doesNotExist();
+
+ assertThatPath(path("/link.txt")).noFollowLinks().isSymbolicLink();
+
+ assertThatPath(path("/link.txt")).isRegularFile().and().containsBytes(bytes);
+ }
+
+ @Test
+ public void testMove_cannotMoveDirIntoOwnSubtree() throws IOException {
+ Files.createDirectories(path("/foo"));
+
+ try {
+ Files.move(path("/foo"), path("/foo/bar"));
+ fail();
+ } catch (IOException expected) {
+ assertThat(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) {
+ assertThat(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());
+ }
+
+ assertThatPath("/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());
+ }
+
+ assertThatPath("/test").containsBytes(bytes).and().attribute("fileKey").is(testKey);
+ }
+
+ @Test
+ public void testMove_toDifferentFileSystem() throws IOException {
+ try (FileSystem fs2 = Jimfs.newFileSystem(Configuration.unix())) {
+ Path foo = fs.getPath("/foo");
+ byte[] bytes = {0, 1, 2, 3, 4};
+ Files.write(foo, bytes);
+ Files.getFileAttributeView(foo, BasicFileAttributeView.class)
+ .setTimes(FileTime.fromMillis(0), FileTime.fromMillis(1), FileTime.fromMillis(2));
+
+ Path foo2 = fs2.getPath("/foo");
+ Files.move(foo, foo2);
+
+ assertThatPath(foo).doesNotExist();
+ assertThatPath(foo2)
+ .exists()
+ .and()
+ .attribute("lastModifiedTime")
+ .is(FileTime.fromMillis(0))
+ .and()
+ .attribute("lastAccessTime")
+ .is(FileTime.fromMillis(1))
+ .and()
+ .attribute("creationTime")
+ .is(FileTime.fromMillis(2))
+ .and()
+ .containsBytes(bytes); // do this last; it updates the access time
+ }
+ }
+
+ @Test
+ public void testIsSameFile() throws IOException {
+ Files.createDirectory(path("/foo"));
+ Files.createSymbolicLink(path("/bar"), path("/foo"));
+ Files.createFile(path("/bar/test"));
+
+ assertThatPath("/foo").isSameFileAs("/foo");
+ assertThatPath("/bar").isSameFileAs("/bar");
+ assertThatPath("/foo/test").isSameFileAs("/foo/test");
+ assertThatPath("/bar/test").isSameFileAs("/bar/test");
+ assertThatPath("/foo").isNotSameFileAs("test");
+ assertThatPath("/bar").isNotSameFileAs("/test");
+ assertThatPath("/foo").isSameFileAs("/bar");
+ assertThatPath("/foo/test").isSameFileAs("/bar/test");
+
+ Files.createSymbolicLink(path("/baz"), path("bar")); // relative path
+ assertThatPath("/baz").isSameFileAs("/foo");
+ assertThatPath("/baz/test").isSameFileAs("/foo/test");
+ }
+
+ @Test
+ public void testIsSameFile_forPathFromDifferentFileSystemProvider() throws IOException {
+ Path defaultFileSystemRoot = FileSystems.getDefault().getRootDirectories().iterator().next();
+
+ assertThat(Files.isSameFile(path("/"), defaultFileSystemRoot)).isFalse();
+ }
+
+ @Test
+ public void testPathLookups() throws IOException {
+ assertThatPath("/").isSameFileAs("/");
+ assertThatPath("/..").isSameFileAs("/");
+ assertThatPath("/../../..").isSameFileAs("/");
+ assertThatPath("../../../..").isSameFileAs("/");
+ assertThatPath("").isSameFileAs("/work");
+
+ Files.createDirectories(path("/foo/bar/baz"));
+ Files.createSymbolicLink(path("/foo/bar/link1"), path("../link2"));
+ Files.createSymbolicLink(path("/foo/link2"), path("/"));
+
+ assertThatPath("/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;
+
+ assertThat(ImmutableList.copyOf(secureStream))
+ .isEqualTo(
+ ImmutableList.of(
+ path("/foo/a"), path("/foo/b"), path("/foo/bar"), path("/foo/barLink")));
+
+ secureStream.deleteFile(path("b"));
+ assertThatPath("/foo/b").doesNotExist();
+
+ secureStream.newByteChannel(path("b"), ImmutableSet.of(WRITE, CREATE_NEW)).close();
+ assertThatPath("/foo/b").isRegularFile();
+
+ assertThatPath("/foo").hasChildren("a", "b", "bar", "barLink");
+
+ Files.createDirectory(path("/baz"));
+ Files.move(path("/foo"), path("/baz/stuff"));
+
+ assertThatPath(path("/foo")).doesNotExist();
+
+ assertThatPath("/baz/stuff").hasChildren("a", "b", "bar", "barLink");
+
+ secureStream.deleteFile(path("b"));
+
+ assertThatPath("/baz/stuff/b").doesNotExist();
+ assertThatPath("/baz/stuff").hasChildren("a", "bar", "barLink");
+
+ assertThat(
+ secureStream
+ .getFileAttributeView(BasicFileAttributeView.class)
+ .readAttributes()
+ .isDirectory())
+ .isTrue();
+
+ assertThat(
+ secureStream
+ .getFileAttributeView(path("a"), BasicFileAttributeView.class)
+ .readAttributes()
+ .isRegularFile())
+ .isTrue();
+
+ try {
+ secureStream.deleteFile(path("bar"));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("bar");
+ }
+
+ try {
+ secureStream.deleteDirectory(path("a"));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("a");
+ }
+
+ try (SecureDirectoryStream<Path> barStream = secureStream.newDirectoryStream(path("bar"))) {
+ barStream.newByteChannel(path("stuff"), ImmutableSet.of(WRITE, CREATE_NEW)).close();
+ assertThat(
+ barStream
+ .getFileAttributeView(path("stuff"), BasicFileAttributeView.class)
+ .readAttributes()
+ .isRegularFile())
+ .isTrue();
+
+ assertThat(
+ secureStream
+ .getFileAttributeView(path("bar/stuff"), BasicFileAttributeView.class)
+ .readAttributes()
+ .isRegularFile())
+ .isTrue();
+ }
+
+ try (SecureDirectoryStream<Path> barLinkStream =
+ secureStream.newDirectoryStream(path("barLink"))) {
+ assertThat(
+ barLinkStream
+ .getFileAttributeView(path("stuff"), BasicFileAttributeView.class)
+ .readAttributes()
+ .isRegularFile())
+ .isTrue();
+
+ assertThat(
+ barLinkStream
+ .getFileAttributeView(path(".."), BasicFileAttributeView.class)
+ .readAttributes()
+ .isDirectory())
+ .isTrue();
+ }
+
+ try {
+ secureStream.newDirectoryStream(path("barLink"), NOFOLLOW_LINKS);
+ fail();
+ } catch (NotDirectoryException expected) {
+ assertThat(expected.getFile()).isEqualTo("barLink");
+ }
+
+ try (SecureDirectoryStream<Path> barStream = secureStream.newDirectoryStream(path("bar"))) {
+ secureStream.move(path("a"), barStream, path("moved"));
+
+ assertThatPath(path("/baz/stuff/a")).doesNotExist();
+ assertThatPath(path("/baz/stuff/bar/moved")).isRegularFile();
+
+ assertThat(
+ 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;
+
+ assertThat(ImmutableList.copyOf(secureStream))
+ .containsExactly(path("foo/a"), path("foo/b"), path("foo/c"));
+
+ try (DirectoryStream<Path> stream2 = secureStream.newDirectoryStream(path("c"))) {
+ assertThat(ImmutableList.copyOf(stream2)).containsExactly(path("foo/c/d"), path("foo/c/e"));
+ }
+ }
+ }
+
+ @SuppressWarnings("StreamResourceLeak")
+ @Test
+ public void testClosedSecureDirectoryStream() throws IOException {
+ Files.createDirectory(path("/foo"));
+ SecureDirectoryStream<Path> stream =
+ (SecureDirectoryStream<Path>) Files.newDirectoryStream(path("/foo"));
+
+ stream.close();
+
+ try {
+ stream.iterator();
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ stream.deleteDirectory(fs.getPath("a"));
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ stream.deleteFile(fs.getPath("a"));
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ stream.newByteChannel(fs.getPath("a"), ImmutableSet.of(CREATE, WRITE));
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ stream.newDirectoryStream(fs.getPath("a"));
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ stream.move(fs.getPath("a"), stream, fs.getPath("b"));
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ stream.getFileAttributeView(BasicFileAttributeView.class);
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ stream.getFileAttributeView(fs.getPath("a"), BasicFileAttributeView.class);
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+ }
+
+ @SuppressWarnings("StreamResourceLeak")
+ @Test
+ public void testClosedSecureDirectoryStreamAttributeViewAndIterator() throws IOException {
+ Files.createDirectory(path("/foo"));
+ Files.createDirectory(path("/foo/bar"));
+ SecureDirectoryStream<Path> stream =
+ (SecureDirectoryStream<Path>) Files.newDirectoryStream(path("/foo"));
+
+ Iterator<Path> iter = stream.iterator();
+ BasicFileAttributeView view1 = stream.getFileAttributeView(BasicFileAttributeView.class);
+ BasicFileAttributeView view2 =
+ stream.getFileAttributeView(path("bar"), BasicFileAttributeView.class);
+
+ try {
+ stream.iterator();
+ fail("expected IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+
+ stream.close();
+
+ try {
+ iter.next();
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ view1.readAttributes();
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ view2.readAttributes();
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ view1.setTimes(null, null, null);
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+
+ try {
+ view2.setTimes(null, null, null);
+ fail("expected ClosedDirectoryStreamException");
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+ }
+
+ @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();
+ }
+
+ @Test
+ public void testUnsupportedFeatures() throws IOException {
+ FileSystem fileSystem =
+ Jimfs.newFileSystem(
+ Configuration.unix().toBuilder()
+ .setSupportedFeatures() // none
+ .build());
+
+ Path foo = fileSystem.getPath("foo");
+ Path bar = foo.resolveSibling("bar");
+
+ try {
+ Files.createLink(foo, bar);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ Files.createSymbolicLink(foo, bar);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ Files.readSymbolicLink(foo);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ FileChannel.open(foo);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ AsynchronousFileChannel.open(foo);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ Files.createDirectory(foo);
+ Files.createFile(bar);
+
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(foo)) {
+ assertThat(stream).isNotInstanceOf(SecureDirectoryStream.class);
+ }
+
+ try (SeekableByteChannel channel = Files.newByteChannel(bar)) {
+ assertThat(channel).isNotInstanceOf(FileChannel.class);
+ }
+ }
+}