diff options
author | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-03-05 04:32:40 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-03-05 04:32:40 +0000 |
commit | e50b3b756101b544da2b0d62041d91547ce14a00 (patch) | |
tree | f8cbc802a806455c5fdfeab7f696c63641b9f9f5 /jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java | |
parent | 68591711a9034281d5fe11fc7a30e535bbce125c (diff) | |
parent | c5f71e95df2f3189f2b68140ebac0c76a238190f (diff) | |
download | jimfs-e50b3b756101b544da2b0d62041d91547ce14a00.tar.gz |
Initial merge with upstream am: cef92d673c am: c5f71e95df
Change-Id: I78d1f8b680646deee89918ad5ff43a8615945c35
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.java | 2401 |
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); + } + } +} |