/* * 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}. * *

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 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> 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> 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> 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> 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 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 stream = Files.newDirectoryStream(path("/foo"))) { if (!(stream instanceof SecureDirectoryStream)) { fail("should be a secure directory stream"); } SecureDirectoryStream secureStream = (SecureDirectoryStream) 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 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 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 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 stream = Files.newDirectoryStream(path("foo"))) { SecureDirectoryStream secureStream = (SecureDirectoryStream) stream; assertThat(ImmutableList.copyOf(secureStream)) .containsExactly(path("foo/a"), path("foo/b"), path("foo/c")); try (DirectoryStream 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 stream = (SecureDirectoryStream) 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 stream = (SecureDirectoryStream) Files.newDirectoryStream(path("/foo")); Iterator 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 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 stream = Files.newDirectoryStream(foo)) { assertThat(stream).isNotInstanceOf(SecureDirectoryStream.class); } try (SeekableByteChannel channel = Files.newByteChannel(bar)) { assertThat(channel).isNotInstanceOf(FileChannel.class); } } }