aboutsummaryrefslogtreecommitdiff
path: root/jimfs/src/test/java
diff options
context:
space:
mode:
Diffstat (limited to 'jimfs/src/test/java')
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java133
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/AbstractGlobMatcherTest.java154
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/AbstractJimfsIntegrationTest.java115
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java259
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/AbstractWatchServiceTest.java253
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/AclAttributeProviderTest.java119
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java391
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java151
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/BasicFileAttribute.java43
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java98
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java120
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java366
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java383
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/DosAttributeProviderTest.java123
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java74
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java178
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/FileTest.java116
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java463
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java229
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java262
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java1049
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java438
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java240
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java205
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsPathTest.java385
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsUnixLikeFileSystemTest.java2401
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/JimfsWindowsLikeFileSystemTest.java491
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/NameTest.java41
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/OwnerAttributeProviderTest.java75
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/PathNormalizationTest.java351
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/PathServiceTest.java264
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/PathSubject.java406
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/PathTester.java187
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java157
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java247
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/PosixAttributeProviderTest.java124
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/RegexGlobMatcherTest.java101
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java145
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java892
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java227
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/TestAttributeView.java30
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/TestAttributes.java29
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/TestUtils.java144
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java92
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/UnixPathTypeTest.java108
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/UrlTest.java127
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/UserDefinedAttributeProviderTest.java134
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/UserLookupServiceTest.java68
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/WatchServiceConfigurationTest.java75
-rw-r--r--jimfs/src/test/java/com/google/common/jimfs/WindowsPathTypeTest.java222
50 files changed, 13485 insertions, 0 deletions
diff --git a/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java
new file mode 100644
index 0000000..7e2bdf9
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/AbstractAttributeProviderTest.java
@@ -0,0 +1,133 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.nio.file.attribute.FileAttributeView;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Before;
+
+/**
+ * Abstract base class for tests of individual {@link AttributeProvider} implementations.
+ *
+ * @author Colin Decker
+ */
+public abstract class AbstractAttributeProviderTest<P extends AttributeProvider> {
+
+ protected static final ImmutableMap<String, FileAttributeView> NO_INHERITED_VIEWS =
+ ImmutableMap.of();
+
+ protected P provider;
+ protected File file;
+
+ /** Create the provider being tested. */
+ protected abstract P createProvider();
+
+ /** Creates the set of providers the provider being tested depends on. */
+ protected abstract Set<? extends AttributeProvider> createInheritedProviders();
+
+ protected FileLookup fileLookup() {
+ return new FileLookup() {
+ @Override
+ public File lookup() throws IOException {
+ return file;
+ }
+ };
+ }
+
+ @Before
+ public void setUp() {
+ this.provider = createProvider();
+ this.file = Directory.create(0);
+
+ Map<String, ?> defaultValues = createDefaultValues();
+ setDefaultValues(file, provider, defaultValues);
+
+ Set<? extends AttributeProvider> inheritedProviders = createInheritedProviders();
+ for (AttributeProvider inherited : inheritedProviders) {
+ setDefaultValues(file, inherited, defaultValues);
+ }
+ }
+
+ private static void setDefaultValues(
+ File file, AttributeProvider provider, Map<String, ?> defaultValues) {
+ Map<String, ?> defaults = provider.defaultValues(defaultValues);
+ for (Map.Entry<String, ?> entry : defaults.entrySet()) {
+ int separatorIndex = entry.getKey().indexOf(':');
+ String view = entry.getKey().substring(0, separatorIndex);
+ String attr = entry.getKey().substring(separatorIndex + 1);
+ file.setAttribute(view, attr, entry.getValue());
+ }
+ }
+
+ protected Map<String, ?> createDefaultValues() {
+ return ImmutableMap.of();
+ }
+
+ // assertions
+
+ protected void assertSupportsAll(String... attributes) {
+ for (String attribute : attributes) {
+ assertThat(provider.supports(attribute)).isTrue();
+ }
+ }
+
+ protected void assertContainsAll(File file, ImmutableMap<String, Object> expectedAttributes) {
+ for (Map.Entry<String, Object> entry : expectedAttributes.entrySet()) {
+ String attribute = entry.getKey();
+ Object value = entry.getValue();
+
+ assertThat(provider.get(file, attribute)).isEqualTo(value);
+ }
+ }
+
+ protected void assertSetAndGetSucceeds(String attribute, Object value) {
+ assertSetAndGetSucceeds(attribute, value, false);
+ }
+
+ protected void assertSetAndGetSucceeds(String attribute, Object value, boolean create) {
+ provider.set(file, provider.name(), attribute, value, create);
+ assertThat(provider.get(file, attribute)).isEqualTo(value);
+ }
+
+ protected void assertSetAndGetSucceedsOnCreate(String attribute, Object value) {
+ assertSetAndGetSucceeds(attribute, value, true);
+ }
+
+ @SuppressWarnings("EmptyCatchBlock")
+ protected void assertSetFails(String attribute, Object value) {
+ try {
+ provider.set(file, provider.name(), attribute, value, false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @SuppressWarnings("EmptyCatchBlock")
+ protected void assertSetFailsOnCreate(String attribute, Object value) {
+ try {
+ provider.set(file, provider.name(), attribute, value, true);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/AbstractGlobMatcherTest.java b/jimfs/src/test/java/com/google/common/jimfs/AbstractGlobMatcherTest.java
new file mode 100644
index 0000000..57936e1
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/AbstractGlobMatcherTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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 org.junit.Test;
+
+/** @author Colin Decker */
+public abstract class AbstractGlobMatcherTest extends AbstractPathMatcherTest {
+
+ @Test
+ public void testMatching_literal() {
+ assertThat("foo").matches("foo");
+ assertThat("/foo").matches("/foo");
+ assertThat("/foo/bar/baz").matches("/foo/bar/baz");
+ }
+
+ @Test
+ public void testMatching_questionMark() {
+ assertThat("?").matches("a", "A", "$", "5", "_").doesNotMatch("/", "ab", "");
+ assertThat("??").matches("ab");
+ assertThat("????").matches("1234");
+ assertThat("?oo?").matches("book", "doom").doesNotMatch("/oom");
+ assertThat("/?oo/ba?").matches("/foo/bar");
+ assertThat("foo.?").matches("foo.h");
+ assertThat("foo.??").matches("foo.cc");
+ }
+
+ @Test
+ public void testMatching_star() {
+ assertThat("*")
+ .matches("a", "abc", "298347829473928423", "abc12345", "")
+ .doesNotMatch("/", "/abc");
+ assertThat("/*").matches("/a", "/abcd", "/abc123", "/").doesNotMatch("/foo/bar");
+ assertThat("/*/*/*")
+ .matches("/a/b/c", "/foo/bar/baz")
+ .doesNotMatch("/foo/bar", "/foo/bar/baz/abc");
+ assertThat("/*/bar").matches("/foo/bar", "/abc/bar").doesNotMatch("/bar");
+ assertThat("/foo/*")
+ .matches("/foo/bar", "/foo/baz")
+ .doesNotMatch("/foo", "foo/bar", "/foo/bar/baz");
+ assertThat("/foo*/ba*")
+ .matches("/food/bar", "/fool/bat", "/foo/ba", "/foot/ba", "/foo/bar", "/foods/bartender")
+ .doesNotMatch("/food/baz/bar");
+ assertThat("*.java")
+ .matches("Foo.java", "Bar.java", "GlobPatternTest.java", "Foo.java.java", ".java")
+ .doesNotMatch("Foo.jav", "Foo", "java.Foo", "Foo.java.");
+ assertThat("Foo.*")
+ .matches("Foo.java", "Foo.txt", "Foo.tar.gz", "Foo.Foo.", "Foo.")
+ .doesNotMatch("Foo", ".Foo");
+ assertThat("*/*.java").matches("foo/Bar.java", "foo/.java");
+ assertThat("*/Bar.*").matches("foo/Bar.java");
+ assertThat(".*").matches(".bashrc", ".bash_profile");
+ assertThat("*.............").matches(
+ "............a............a..............a.............a............a.........." +
+ ".........................................................a....................");
+ assertThat("*.............*..").matches(
+ "............a............a..............a.............a............a.........." +
+ "..........a...................................................................");
+ assertThat(".................*........*.*.....*....................*..............*").matches(
+ ".................................abc.........................................." +
+ ".............................................................................." +
+ ".............................................................................." +
+ ".............................................12..............................." +
+ ".........................................................................hello" +
+ "..............................................................................");
+ }
+
+ @Test
+ public void testMatching_starStar() {
+ assertThat("**")
+ .matches("", "a", "abc", "293874982374913794141", "/foo/bar/baz", "foo/bar.txt");
+ assertThat("**foo")
+ .matches("foo", "barfoo", "/foo", "/a/b/c/foo", "c.foo", "a/b/c.foo")
+ .doesNotMatch("foo.bar", "/a/b/food");
+ assertThat("/foo/**/bar.txt")
+ .matches("/foo/baz/bar.txt", "/foo/bar/asdf/bar.txt")
+ .doesNotMatch("/foo/bar.txt", "/foo/baz/bar");
+ assertThat("**/*.java").matches("/Foo.java", "foo/Bar.java", "/.java", "foo/.java");
+ }
+
+ @Test
+ public void testMatching_brackets() {
+ assertThat("[ab]").matches("a", "b").doesNotMatch("ab", "ba", "aa", "bb", "c", "", "/");
+ assertThat("[a-d]")
+ .matches("a", "b", "c", "d")
+ .doesNotMatch("e", "f", "z", "aa", "ab", "abcd", "", "/");
+ assertThat("[a-dz]")
+ .matches("a", "b", "c", "d", "z")
+ .doesNotMatch("e", "f", "aa", "ab", "dz", "", "/");
+ assertThat("[!b]").matches("a", "c", "d", "0", "!", "$").doesNotMatch("b", "/", "", "ac");
+ assertThat("[!b-d3]")
+ .matches("a", "e", "f", "0", "1", "2", "4")
+ .doesNotMatch("b", "c", "d", "3");
+ assertThat("[-]").matches("-");
+ assertThat("[-a-c]").matches("-", "a", "b", "c");
+ assertThat("[!-a-c]").matches("d", "e", "0").doesNotMatch("a", "b", "c", "-");
+ assertThat("[\\d]").matches("\\", "d").doesNotMatch("0", "1");
+ assertThat("[\\s]").matches("\\", "s").doesNotMatch(" ");
+ assertThat("[\\]").matches("\\").doesNotMatch("]");
+ }
+
+ @Test
+ public void testMatching_curlyBraces() {
+ assertThat("{a,b}").matches("a", "b").doesNotMatch("/", "c", "0", "", ",", "{", "}");
+ assertThat("{ab,cd}").matches("ab", "cd").doesNotMatch("bc", "ac", "ad", "ba", "dc", ",");
+ assertThat(".{h,cc}").matches(".h", ".cc").doesNotMatch("h", "cc");
+ assertThat("{?oo,ba?}").matches("foo", "boo", "moo", "bat", "bar", "baz");
+ assertThat("{[Ff]oo*,[Bb]a*,[A-Ca-c]*/[!z]*.txt}")
+ .matches("foo", "Foo", "fools", "ba", "Ba", "bar", "Bar", "Bart", "c/y.txt", "Cat/foo.txt")
+ .doesNotMatch("Cat", "Cat/foo", "blah", "bAr", "c/z.txt", "c/.txt", "*");
+ }
+
+ @Test
+ public void testMatching_escapes() {
+ assertThat("\\\\").matches("\\");
+ assertThat("\\*").matches("*");
+ assertThat("\\*\\*").matches("**");
+ assertThat("\\[").matches("[");
+ assertThat("\\{").matches("{");
+ assertThat("\\a").matches("a");
+ assertThat("{a,\\}}").matches("a", "}");
+ assertThat("{a\\,,b}").matches("a,", "b").doesNotMatch("a", ",");
+ }
+
+ @Test
+ public void testMatching_various() {
+ assertThat("**/[A-Z]*.{[Jj][Aa][Vv][Aa],[Tt][Xx][Tt]}")
+ .matches("/foo/bar/Baz.java", "/A.java", "bar/Test.JAVA", "foo/Foo.tXt");
+ }
+
+ @Test
+ public void testInvalidSyntax() {
+ assertSyntaxError("\\");
+ assertSyntaxError("[");
+ assertSyntaxError("[]");
+ assertSyntaxError("{");
+ assertSyntaxError("{{}");
+ assertSyntaxError("{a,b,a{b,c},d}");
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/AbstractJimfsIntegrationTest.java b/jimfs/src/test/java/com/google/common/jimfs/AbstractJimfsIntegrationTest.java
new file mode 100644
index 0000000..11a0944
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/AbstractJimfsIntegrationTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.PathSubject.paths;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import java.io.IOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import org.junit.After;
+import org.junit.Before;
+
+/** @author Colin Decker */
+public abstract class AbstractJimfsIntegrationTest {
+
+ protected FileSystem fs;
+
+ @Before
+ public void setUp() throws IOException {
+ fs = createFileSystem();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ fs.close();
+ }
+
+ /** Creates the file system to use in the tests. */
+ protected abstract FileSystem createFileSystem();
+
+ // helpers
+
+ protected Path path(String first, String... more) {
+ return fs.getPath(first, more);
+ }
+
+ protected Object getFileKey(String path, LinkOption... options) throws IOException {
+ return Files.getAttribute(path(path), "fileKey", options);
+ }
+
+ protected PathSubject assertThatPath(String path, LinkOption... options) {
+ return assertThatPath(path(path), options);
+ }
+
+ protected static PathSubject assertThatPath(Path path, LinkOption... options) {
+ PathSubject subject = assert_().about(paths()).that(path);
+ if (options.length != 0) {
+ subject = subject.noFollowLinks();
+ }
+ return subject;
+ }
+
+ /** Tester for testing changes in file times. */
+ protected static final class FileTimeTester {
+
+ private final Path path;
+
+ private FileTime accessTime;
+ private FileTime modifiedTime;
+
+ FileTimeTester(Path path) throws IOException {
+ this.path = path;
+
+ BasicFileAttributes attrs = attrs();
+ accessTime = attrs.lastAccessTime();
+ modifiedTime = attrs.lastModifiedTime();
+ }
+
+ private BasicFileAttributes attrs() throws IOException {
+ return Files.readAttributes(path, BasicFileAttributes.class);
+ }
+
+ public void assertAccessTimeChanged() throws IOException {
+ FileTime t = attrs().lastAccessTime();
+ assertThat(t).isNotEqualTo(accessTime);
+ accessTime = t;
+ }
+
+ public void assertAccessTimeDidNotChange() throws IOException {
+ FileTime t = attrs().lastAccessTime();
+ assertThat(t).isEqualTo(accessTime);
+ }
+
+ public void assertModifiedTimeChanged() throws IOException {
+ FileTime t = attrs().lastModifiedTime();
+ assertThat(t).isNotEqualTo(modifiedTime);
+ modifiedTime = t;
+ }
+
+ public void assertModifiedTimeDidNotChange() throws IOException {
+ FileTime t = attrs().lastModifiedTime();
+ assertThat(t).isEqualTo(modifiedTime);
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java b/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java
new file mode 100644
index 0000000..70ac0e9
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/AbstractPathMatcherTest.java
@@ -0,0 +1,259 @@
+/*
+ * 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 org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.nio.file.Paths;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Iterator;
+import java.util.regex.PatternSyntaxException;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+
+/**
+ * Abstract base class for tests of {@link PathMatcher} implementations.
+ *
+ * @author Colin Decker
+ */
+public abstract class AbstractPathMatcherTest {
+
+ /**
+ * Creates a new {@code PathMatcher} using the given pattern in the syntax this test is testing.
+ */
+ protected abstract PathMatcher matcher(String pattern);
+
+ /** Override to return a real matcher for the given pattern. */
+ @NullableDecl
+ protected PathMatcher realMatcher(String pattern) {
+ return null;
+ }
+
+ protected void assertSyntaxError(String pattern) {
+ try {
+ matcher(pattern);
+ fail();
+ } catch (PatternSyntaxException expected) {
+ }
+
+ try {
+ PathMatcher real = realMatcher(pattern);
+ if (real != null) {
+ fail();
+ }
+ } catch (PatternSyntaxException expected) {
+ }
+ }
+
+ protected final PatternAsserter assertThat(String pattern) {
+ return new PatternAsserter(pattern);
+ }
+
+ protected final class PatternAsserter {
+
+ private final PathMatcher matcher;
+
+ @NullableDecl private final PathMatcher realMatcher;
+
+ PatternAsserter(String pattern) {
+ this.matcher = matcher(pattern);
+ this.realMatcher = realMatcher(pattern);
+ }
+
+ PatternAsserter matches(String... paths) {
+ for (String path : paths) {
+ assertTrue(
+ "matcher '" + matcher + "' did not match '" + path + "'", matcher.matches(fake(path)));
+ if (realMatcher != null) {
+ Path realPath = Paths.get(path);
+ assertTrue(
+ "real matcher '" + realMatcher + "' did not match '" + realPath + "'",
+ realMatcher.matches(realPath));
+ }
+ }
+ return this;
+ }
+
+ PatternAsserter doesNotMatch(String... paths) {
+ for (String path : paths) {
+ assertFalse(
+ "glob '" + matcher + "' should not have matched '" + path + "'",
+ matcher.matches(fake(path)));
+ if (realMatcher != null) {
+ Path realPath = Paths.get(path);
+ assertFalse(
+ "real matcher '" + realMatcher + "' matched '" + realPath + "'",
+ realMatcher.matches(realPath));
+ }
+ }
+ return this;
+ }
+ }
+
+ /** Path that only provides toString(). */
+ private static Path fake(final String path) {
+ return new Path() {
+ @Override
+ public FileSystem getFileSystem() {
+ return null;
+ }
+
+ @Override
+ public boolean isAbsolute() {
+ return false;
+ }
+
+ @Override
+ public Path getRoot() {
+ return null;
+ }
+
+ @Override
+ public Path getFileName() {
+ return null;
+ }
+
+ @Override
+ public Path getParent() {
+ return null;
+ }
+
+ @Override
+ public int getNameCount() {
+ return 0;
+ }
+
+ @Override
+ public Path getName(int index) {
+ return null;
+ }
+
+ @Override
+ public Path subpath(int beginIndex, int endIndex) {
+ return null;
+ }
+
+ @Override
+ public boolean startsWith(Path other) {
+ return false;
+ }
+
+ @Override
+ public boolean startsWith(String other) {
+ return false;
+ }
+
+ @Override
+ public boolean endsWith(Path other) {
+ return false;
+ }
+
+ @Override
+ public boolean endsWith(String other) {
+ return false;
+ }
+
+ @Override
+ public Path normalize() {
+ return null;
+ }
+
+ @Override
+ public Path resolve(Path other) {
+ return null;
+ }
+
+ @Override
+ public Path resolve(String other) {
+ return null;
+ }
+
+ @Override
+ public Path resolveSibling(Path other) {
+ return null;
+ }
+
+ @Override
+ public Path resolveSibling(String other) {
+ return null;
+ }
+
+ @Override
+ public Path relativize(Path other) {
+ return null;
+ }
+
+ @Override
+ public URI toUri() {
+ return null;
+ }
+
+ @Override
+ public Path toAbsolutePath() {
+ return null;
+ }
+
+ @Override
+ public Path toRealPath(LinkOption... options) throws IOException {
+ return null;
+ }
+
+ @Override
+ public File toFile() {
+ return null;
+ }
+
+ @Override
+ public WatchKey register(
+ WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events)
+ throws IOException {
+ return null;
+ }
+
+ @Override
+ public Iterator<Path> iterator() {
+ return null;
+ }
+
+ @Override
+ public int compareTo(Path other) {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ return path;
+ }
+ };
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/AbstractWatchServiceTest.java b/jimfs/src/test/java/com/google/common/jimfs/AbstractWatchServiceTest.java
new file mode 100644
index 0000000..61ddeb8
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/AbstractWatchServiceTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.AbstractWatchService.Key.State.READY;
+import static com.google.common.jimfs.AbstractWatchService.Key.State.SIGNALLED;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.ClosedWatchServiceException;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.nio.file.Watchable;
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link AbstractWatchService}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class AbstractWatchServiceTest {
+
+ private AbstractWatchService watcher;
+
+ @Before
+ public void setUp() throws IOException {
+ watcher = new AbstractWatchService() {};
+ }
+
+ @Test
+ public void testNewWatcher() throws IOException {
+ assertThat(watcher.isOpen()).isTrue();
+ assertThat(watcher.poll()).isNull();
+ assertThat(watcher.queuedKeys()).isEmpty();
+ watcher.close();
+ assertThat(watcher.isOpen()).isFalse();
+ }
+
+ @Test
+ public void testRegister() throws IOException {
+ Watchable watchable = new StubWatchable();
+ AbstractWatchService.Key key = watcher.register(watchable, ImmutableSet.of(ENTRY_CREATE));
+ assertThat(key.isValid()).isTrue();
+ assertThat(key.pollEvents()).isEmpty();
+ assertThat(key.subscribesTo(ENTRY_CREATE)).isTrue();
+ assertThat(key.subscribesTo(ENTRY_DELETE)).isFalse();
+ assertThat(key.watchable()).isEqualTo(watchable);
+ assertThat(key.state()).isEqualTo(READY);
+ }
+
+ @Test
+ public void testPostEvent() throws IOException {
+ AbstractWatchService.Key key =
+ watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE));
+
+ AbstractWatchService.Event<Path> event =
+ new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null);
+ key.post(event);
+ key.signal();
+
+ assertThat(watcher.queuedKeys()).containsExactly(key);
+
+ WatchKey retrievedKey = watcher.poll();
+ assertThat(retrievedKey).isEqualTo(key);
+
+ List<WatchEvent<?>> events = retrievedKey.pollEvents();
+ assertThat(events).hasSize(1);
+ assertThat(events.get(0)).isEqualTo(event);
+
+ // polling should have removed all events
+ assertThat(retrievedKey.pollEvents()).isEmpty();
+ }
+
+ @Test
+ public void testKeyStates() throws IOException {
+ AbstractWatchService.Key key =
+ watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE));
+
+ AbstractWatchService.Event<Path> event =
+ new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null);
+ assertThat(key.state()).isEqualTo(READY);
+ key.post(event);
+ key.signal();
+ assertThat(key.state()).isEqualTo(SIGNALLED);
+
+ AbstractWatchService.Event<Path> event2 =
+ new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null);
+ key.post(event2);
+ assertThat(key.state()).isEqualTo(SIGNALLED);
+
+ // key was not queued twice
+ assertThat(watcher.queuedKeys()).containsExactly(key);
+ assertThat(watcher.poll().pollEvents()).containsExactly(event, event2);
+
+ assertThat(watcher.poll()).isNull();
+
+ key.post(event);
+
+ // still not added to queue; already signalled
+ assertThat(watcher.poll()).isNull();
+ assertThat(key.pollEvents()).containsExactly(event);
+
+ key.reset();
+ assertThat(key.state()).isEqualTo(READY);
+
+ key.post(event2);
+ key.signal();
+
+ // now that it's reset it can be requeued
+ assertThat(watcher.poll()).isEqualTo(key);
+ }
+
+ @Test
+ public void testKeyRequeuedOnResetIfEventsArePending() throws IOException {
+ AbstractWatchService.Key key =
+ watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE));
+ key.post(new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null));
+ key.signal();
+
+ key = (AbstractWatchService.Key) watcher.poll();
+ assertThat(watcher.queuedKeys()).isEmpty();
+
+ assertThat(key.pollEvents()).hasSize(1);
+
+ key.post(new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null));
+ assertThat(watcher.queuedKeys()).isEmpty();
+
+ key.reset();
+ assertThat(key.state()).isEqualTo(SIGNALLED);
+ assertThat(watcher.queuedKeys()).hasSize(1);
+ }
+
+ @Test
+ public void testOverflow() throws IOException {
+ AbstractWatchService.Key key =
+ watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE));
+ for (int i = 0; i < AbstractWatchService.Key.MAX_QUEUE_SIZE + 10; i++) {
+ key.post(new AbstractWatchService.Event<>(ENTRY_CREATE, 1, null));
+ }
+ key.signal();
+
+ List<WatchEvent<?>> events = key.pollEvents();
+
+ assertThat(events).hasSize(AbstractWatchService.Key.MAX_QUEUE_SIZE + 1);
+ for (int i = 0; i < AbstractWatchService.Key.MAX_QUEUE_SIZE; i++) {
+ assertThat(events.get(i).kind()).isEqualTo(ENTRY_CREATE);
+ }
+
+ WatchEvent<?> lastEvent = events.get(AbstractWatchService.Key.MAX_QUEUE_SIZE);
+ assertThat(lastEvent.kind()).isEqualTo(OVERFLOW);
+ assertThat(lastEvent.count()).isEqualTo(10);
+ }
+
+ @Test
+ public void testResetAfterCancelReturnsFalse() throws IOException {
+ AbstractWatchService.Key key =
+ watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE));
+ key.signal();
+ key.cancel();
+ assertThat(key.reset()).isFalse();
+ }
+
+ @Test
+ public void testClosedWatcher() throws IOException, InterruptedException {
+ AbstractWatchService.Key key1 =
+ watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_CREATE));
+ AbstractWatchService.Key key2 =
+ watcher.register(new StubWatchable(), ImmutableSet.of(ENTRY_MODIFY));
+
+ assertThat(key1.isValid()).isTrue();
+ assertThat(key2.isValid()).isTrue();
+
+ watcher.close();
+
+ assertThat(key1.isValid()).isFalse();
+ assertThat(key2.isValid()).isFalse();
+ assertThat(key1.reset()).isFalse();
+ assertThat(key2.reset()).isFalse();
+
+ try {
+ watcher.poll();
+ fail();
+ } catch (ClosedWatchServiceException expected) {
+ }
+
+ try {
+ watcher.poll(10, SECONDS);
+ fail();
+ } catch (ClosedWatchServiceException expected) {
+ }
+
+ try {
+ watcher.take();
+ fail();
+ } catch (ClosedWatchServiceException expected) {
+ }
+
+ try {
+ watcher.register(new StubWatchable(), ImmutableList.<WatchEvent.Kind<?>>of());
+ fail();
+ } catch (ClosedWatchServiceException expected) {
+ }
+ }
+
+ // TODO(cgdecker): Test concurrent use of Watcher
+
+ /** A fake {@link Watchable} for testing. */
+ private static final class StubWatchable implements Watchable {
+
+ @Override
+ public WatchKey register(
+ WatchService watcher, WatchEvent.Kind<?>[] events, WatchEvent.Modifier... modifiers)
+ throws IOException {
+ return register(watcher, events);
+ }
+
+ @Override
+ public WatchKey register(WatchService watcher, WatchEvent.Kind<?>... events)
+ throws IOException {
+ return ((AbstractWatchService) watcher).register(this, Arrays.asList(events));
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/AclAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/AclAttributeProviderTest.java
new file mode 100644
index 0000000..f8a9445
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/AclAttributeProviderTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.UserLookupService.createUserPrincipal;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.file.attribute.AclEntryFlag.DIRECTORY_INHERIT;
+import static java.nio.file.attribute.AclEntryPermission.APPEND_DATA;
+import static java.nio.file.attribute.AclEntryPermission.DELETE;
+import static java.nio.file.attribute.AclEntryType.ALLOW;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.attribute.AclEntry;
+import java.nio.file.attribute.AclFileAttributeView;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.UserPrincipal;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link AclAttributeProvider}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class AclAttributeProviderTest extends AbstractAttributeProviderTest<AclAttributeProvider> {
+
+ private static final UserPrincipal USER = createUserPrincipal("user");
+ private static final UserPrincipal FOO = createUserPrincipal("foo");
+
+ private static final ImmutableList<AclEntry> defaultAcl =
+ new ImmutableList.Builder<AclEntry>()
+ .add(
+ AclEntry.newBuilder()
+ .setType(ALLOW)
+ .setFlags(DIRECTORY_INHERIT)
+ .setPermissions(DELETE, APPEND_DATA)
+ .setPrincipal(USER)
+ .build())
+ .add(
+ AclEntry.newBuilder()
+ .setType(ALLOW)
+ .setFlags(DIRECTORY_INHERIT)
+ .setPermissions(DELETE, APPEND_DATA)
+ .setPrincipal(FOO)
+ .build())
+ .build();
+
+ @Override
+ protected AclAttributeProvider createProvider() {
+ return new AclAttributeProvider();
+ }
+
+ @Override
+ protected Set<? extends AttributeProvider> createInheritedProviders() {
+ return ImmutableSet.of(new BasicAttributeProvider(), new OwnerAttributeProvider());
+ }
+
+ @Override
+ protected Map<String, ?> createDefaultValues() {
+ return ImmutableMap.of("acl:acl", defaultAcl);
+ }
+
+ @Test
+ public void testInitialAttributes() {
+ assertThat(provider.get(file, "acl")).isEqualTo(defaultAcl);
+ }
+
+ @Test
+ public void testSet() {
+ assertSetAndGetSucceeds("acl", ImmutableList.of());
+ assertSetFailsOnCreate("acl", ImmutableList.of());
+ assertSetFails("acl", ImmutableSet.of());
+ assertSetFails("acl", ImmutableList.of("hello"));
+ }
+
+ @Test
+ public void testView() throws IOException {
+ AclFileAttributeView view =
+ provider.view(
+ fileLookup(),
+ ImmutableMap.<String, FileAttributeView>of(
+ "owner", new OwnerAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS)));
+ assertNotNull(view);
+
+ assertThat(view.name()).isEqualTo("acl");
+
+ assertThat(view.getAcl()).isEqualTo(defaultAcl);
+
+ view.setAcl(ImmutableList.<AclEntry>of());
+ view.setOwner(FOO);
+
+ assertThat(view.getAcl()).isEqualTo(ImmutableList.<AclEntry>of());
+ assertThat(view.getOwner()).isEqualTo(FOO);
+
+ assertThat(file.getAttribute("acl", "acl")).isEqualTo(ImmutableList.<AclEntry>of());
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java b/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java
new file mode 100644
index 0000000..80b0191
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/AttributeServiceTest.java
@@ -0,0 +1,391 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link AttributeService}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class AttributeServiceTest {
+
+ private AttributeService service;
+
+ @Before
+ public void setUp() {
+ ImmutableSet<AttributeProvider> providers =
+ ImmutableSet.of(
+ StandardAttributeProviders.get("basic"),
+ StandardAttributeProviders.get("owner"),
+ new TestAttributeProvider());
+ service = new AttributeService(providers, ImmutableMap.<String, Object>of());
+ }
+
+ @Test
+ public void testSupportedFileAttributeViews() {
+ assertThat(service.supportedFileAttributeViews())
+ .isEqualTo(ImmutableSet.of("basic", "test", "owner"));
+ }
+
+ @Test
+ public void testSupportsFileAttributeView() {
+ assertThat(service.supportsFileAttributeView(BasicFileAttributeView.class)).isTrue();
+ assertThat(service.supportsFileAttributeView(TestAttributeView.class)).isTrue();
+ assertThat(service.supportsFileAttributeView(PosixFileAttributeView.class)).isFalse();
+ }
+
+ @Test
+ public void testSetInitialAttributes() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file);
+
+ assertThat(file.getAttributeNames("test")).containsExactly("bar", "baz");
+ assertThat(file.getAttributeNames("owner")).containsExactly("owner");
+
+ assertThat(service.getAttribute(file, "basic:lastModifiedTime")).isInstanceOf(FileTime.class);
+ assertThat(file.getAttribute("test", "bar")).isEqualTo(0L);
+ assertThat(file.getAttribute("test", "baz")).isEqualTo(1);
+ }
+
+ @Test
+ public void testGetAttribute() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file);
+
+ assertThat(service.getAttribute(file, "test:foo")).isEqualTo("hello");
+ assertThat(service.getAttribute(file, "test", "foo")).isEqualTo("hello");
+ assertThat(service.getAttribute(file, "basic:isRegularFile")).isEqualTo(false);
+ assertThat(service.getAttribute(file, "isDirectory")).isEqualTo(true);
+ assertThat(service.getAttribute(file, "test:baz")).isEqualTo(1);
+ }
+
+ @Test
+ public void testGetAttribute_fromInheritedProvider() {
+ File file = Directory.create(0);
+ assertThat(service.getAttribute(file, "test:isRegularFile")).isEqualTo(false);
+ assertThat(service.getAttribute(file, "test:isDirectory")).isEqualTo(true);
+ assertThat(service.getAttribute(file, "test", "fileKey")).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetAttribute_failsForAttributesNotDefinedByProvider() {
+ File file = Directory.create(0);
+ try {
+ service.getAttribute(file, "test:blah");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ // baz is defined by "test", but basic doesn't inherit test
+ service.getAttribute(file, "basic", "baz");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetAttribute() {
+ File file = Directory.create(0);
+ service.setAttribute(file, "test:bar", 10L, false);
+ assertThat(file.getAttribute("test", "bar")).isEqualTo(10L);
+
+ service.setAttribute(file, "test:baz", 100, false);
+ assertThat(file.getAttribute("test", "baz")).isEqualTo(100);
+ }
+
+ @Test
+ public void testSetAttribute_forInheritedProvider() {
+ File file = Directory.create(0);
+ service.setAttribute(file, "test:lastModifiedTime", FileTime.fromMillis(0), false);
+ assertThat(file.getAttribute("test", "lastModifiedTime")).isNull();
+ assertThat(service.getAttribute(file, "basic:lastModifiedTime"))
+ .isEqualTo(FileTime.fromMillis(0));
+ }
+
+ @Test
+ public void testSetAttribute_withAlternateAcceptedType() {
+ File file = Directory.create(0);
+ service.setAttribute(file, "test:bar", 10F, false);
+ assertThat(file.getAttribute("test", "bar")).isEqualTo(10L);
+
+ service.setAttribute(file, "test:bar", BigInteger.valueOf(123), false);
+ assertThat(file.getAttribute("test", "bar")).isEqualTo(123L);
+ }
+
+ @Test
+ public void testSetAttribute_onCreate() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file, new BasicFileAttribute<>("test:baz", 123));
+ assertThat(file.getAttribute("test", "baz")).isEqualTo(123);
+ }
+
+ @Test
+ public void testSetAttribute_failsForAttributesNotDefinedByProvider() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file);
+
+ try {
+ service.setAttribute(file, "test:blah", "blah", false);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ try {
+ // baz is defined by "test", but basic doesn't inherit test
+ service.setAttribute(file, "basic:baz", 5, false);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+
+ assertThat(file.getAttribute("test", "baz")).isEqualTo(1);
+ }
+
+ @Test
+ public void testSetAttribute_failsForArgumentThatIsNotOfCorrectType() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file);
+ try {
+ service.setAttribute(file, "test:bar", "wrong", false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ assertThat(file.getAttribute("test", "bar")).isEqualTo(0L);
+ }
+
+ @Test
+ public void testSetAttribute_failsForNullArgument() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file);
+ try {
+ service.setAttribute(file, "test:bar", null, false);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+
+ assertThat(file.getAttribute("test", "bar")).isEqualTo(0L);
+ }
+
+ @Test
+ public void testSetAttribute_failsForAttributeThatIsNotSettable() {
+ File file = Directory.create(0);
+ try {
+ service.setAttribute(file, "test:foo", "world", false);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ assertThat(file.getAttribute("test", "foo")).isNull();
+ }
+
+ @Test
+ public void testSetAttribute_onCreate_failsForAttributeThatIsNotSettableOnCreate() {
+ File file = Directory.create(0);
+ try {
+ service.setInitialAttributes(file, new BasicFileAttribute<>("test:foo", "world"));
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ // it turns out that UOE should be thrown on create even if the attribute isn't settable
+ // under any circumstances
+ }
+
+ try {
+ service.setInitialAttributes(file, new BasicFileAttribute<>("test:bar", 5));
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test
+ public void testGetFileAttributeView() throws IOException {
+ final File file = Directory.create(0);
+ service.setInitialAttributes(file);
+
+ FileLookup fileLookup =
+ new FileLookup() {
+ @Override
+ public File lookup() throws IOException {
+ return file;
+ }
+ };
+
+ assertThat(service.getFileAttributeView(fileLookup, TestAttributeView.class)).isNotNull();
+ assertThat(service.getFileAttributeView(fileLookup, BasicFileAttributeView.class)).isNotNull();
+
+ TestAttributes attrs =
+ service.getFileAttributeView(fileLookup, TestAttributeView.class).readAttributes();
+ assertThat(attrs.foo()).isEqualTo("hello");
+ assertThat(attrs.bar()).isEqualTo(0);
+ assertThat(attrs.baz()).isEqualTo(1);
+ }
+
+ @Test
+ public void testGetFileAttributeView_isNullForUnsupportedView() {
+ final File file = Directory.create(0);
+ FileLookup fileLookup =
+ new FileLookup() {
+ @Override
+ public File lookup() throws IOException {
+ return file;
+ }
+ };
+ assertThat(service.getFileAttributeView(fileLookup, PosixFileAttributeView.class)).isNull();
+ }
+
+ @Test
+ public void testReadAttributes_asMap() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file);
+
+ ImmutableMap<String, Object> map = service.readAttributes(file, "test:foo,bar,baz");
+ assertThat(map).isEqualTo(ImmutableMap.of("foo", "hello", "bar", 0L, "baz", 1));
+
+ FileTime time = (FileTime) service.getAttribute(file, "basic:creationTime");
+
+ map = service.readAttributes(file, "test:*");
+ assertThat(map)
+ .isEqualTo(
+ ImmutableMap.<String, Object>builder()
+ .put("foo", "hello")
+ .put("bar", 0L)
+ .put("baz", 1)
+ .put("fileKey", 0)
+ .put("isDirectory", true)
+ .put("isRegularFile", false)
+ .put("isSymbolicLink", false)
+ .put("isOther", false)
+ .put("size", 0L)
+ .put("lastModifiedTime", time)
+ .put("lastAccessTime", time)
+ .put("creationTime", time)
+ .build());
+
+ map = service.readAttributes(file, "basic:*");
+ assertThat(map)
+ .isEqualTo(
+ ImmutableMap.<String, Object>builder()
+ .put("fileKey", 0)
+ .put("isDirectory", true)
+ .put("isRegularFile", false)
+ .put("isSymbolicLink", false)
+ .put("isOther", false)
+ .put("size", 0L)
+ .put("lastModifiedTime", time)
+ .put("lastAccessTime", time)
+ .put("creationTime", time)
+ .build());
+ }
+
+ @Test
+ public void testReadAttributes_asMap_failsForInvalidAttributes() {
+ File file = Directory.create(0);
+ try {
+ service.readAttributes(file, "basic:fileKey,isOther,*,creationTime");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("invalid attributes");
+ }
+
+ try {
+ service.readAttributes(file, "basic:fileKey,isOther,foo");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("invalid attribute");
+ }
+ }
+
+ @Test
+ public void testReadAttributes_asObject() {
+ File file = Directory.create(0);
+ service.setInitialAttributes(file);
+
+ BasicFileAttributes basicAttrs = service.readAttributes(file, BasicFileAttributes.class);
+ assertThat(basicAttrs.fileKey()).isEqualTo(0);
+ assertThat(basicAttrs.isDirectory()).isTrue();
+ assertThat(basicAttrs.isRegularFile()).isFalse();
+
+ TestAttributes testAttrs = service.readAttributes(file, TestAttributes.class);
+ assertThat(testAttrs.foo()).isEqualTo("hello");
+ assertThat(testAttrs.bar()).isEqualTo(0);
+ assertThat(testAttrs.baz()).isEqualTo(1);
+
+ file.setAttribute("test", "baz", 100);
+ assertThat(service.readAttributes(file, TestAttributes.class).baz()).isEqualTo(100);
+ }
+
+ @Test
+ public void testReadAttributes_failsForUnsupportedAttributesType() {
+ File file = Directory.create(0);
+ try {
+ service.readAttributes(file, PosixFileAttributes.class);
+ fail();
+ } catch (UnsupportedOperationException expected) {
+ }
+ }
+
+ @Test
+ public void testIllegalAttributeFormats() {
+ File file = Directory.create(0);
+ try {
+ service.getAttribute(file, ":bar");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("attribute format");
+ }
+
+ try {
+ service.getAttribute(file, "test:");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("attribute format");
+ }
+
+ try {
+ service.getAttribute(file, "basic:test:isDirectory");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("attribute format");
+ }
+
+ try {
+ service.getAttribute(file, "basic:fileKey,size");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("single attribute");
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java
new file mode 100644
index 0000000..a101b78
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/BasicAttributeProviderTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link BasicAttributeProvider}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class BasicAttributeProviderTest
+ extends AbstractAttributeProviderTest<BasicAttributeProvider> {
+
+ @Override
+ protected BasicAttributeProvider createProvider() {
+ return new BasicAttributeProvider();
+ }
+
+ @Override
+ protected Set<? extends AttributeProvider> createInheritedProviders() {
+ return ImmutableSet.of();
+ }
+
+ @Test
+ public void testSupportedAttributes() {
+ assertSupportsAll(
+ "fileKey",
+ "size",
+ "isDirectory",
+ "isRegularFile",
+ "isSymbolicLink",
+ "isOther",
+ "creationTime",
+ "lastModifiedTime",
+ "lastAccessTime");
+ }
+
+ @Test
+ public void testInitialAttributes() {
+ long time = file.getCreationTime();
+ assertThat(time).isNotEqualTo(0L);
+ assertThat(time).isEqualTo(file.getLastAccessTime());
+ assertThat(time).isEqualTo(file.getLastModifiedTime());
+
+ assertContainsAll(
+ file,
+ ImmutableMap.<String, Object>builder()
+ .put("fileKey", 0)
+ .put("size", 0L)
+ .put("isDirectory", true)
+ .put("isRegularFile", false)
+ .put("isSymbolicLink", false)
+ .put("isOther", false)
+ .build());
+ }
+
+ @Test
+ public void testSet() {
+ FileTime time = FileTime.fromMillis(0L);
+
+ // settable
+ assertSetAndGetSucceeds("creationTime", time);
+ assertSetAndGetSucceeds("lastModifiedTime", time);
+ assertSetAndGetSucceeds("lastAccessTime", time);
+
+ // unsettable
+ assertSetFails("fileKey", 3L);
+ assertSetFails("size", 1L);
+ assertSetFails("isRegularFile", true);
+ assertSetFails("isDirectory", true);
+ assertSetFails("isSymbolicLink", true);
+ assertSetFails("isOther", true);
+
+ // invalid type
+ assertSetFails("creationTime", "foo");
+ }
+
+ @Test
+ public void testSetOnCreate() {
+ FileTime time = FileTime.fromMillis(0L);
+
+ assertSetFailsOnCreate("creationTime", time);
+ assertSetFailsOnCreate("lastModifiedTime", time);
+ assertSetFailsOnCreate("lastAccessTime", time);
+ }
+
+ @Test
+ public void testView() throws IOException {
+ BasicFileAttributeView view = provider.view(fileLookup(), NO_INHERITED_VIEWS);
+
+ assertThat(view).isNotNull();
+ assertThat(view.name()).isEqualTo("basic");
+
+ BasicFileAttributes attrs = view.readAttributes();
+ assertThat(attrs.fileKey()).isEqualTo(0);
+
+ FileTime time = attrs.creationTime();
+ assertThat(attrs.lastAccessTime()).isEqualTo(time);
+ assertThat(attrs.lastModifiedTime()).isEqualTo(time);
+
+ view.setTimes(null, null, null);
+
+ attrs = view.readAttributes();
+ assertThat(attrs.creationTime()).isEqualTo(time);
+ assertThat(attrs.lastAccessTime()).isEqualTo(time);
+ assertThat(attrs.lastModifiedTime()).isEqualTo(time);
+
+ view.setTimes(FileTime.fromMillis(0L), null, null);
+
+ attrs = view.readAttributes();
+ assertThat(attrs.creationTime()).isEqualTo(time);
+ assertThat(attrs.lastAccessTime()).isEqualTo(time);
+ assertThat(attrs.lastModifiedTime()).isEqualTo(FileTime.fromMillis(0L));
+ }
+
+ @Test
+ public void testAttributes() {
+ BasicFileAttributes attrs = provider.readAttributes(file);
+ assertThat(attrs.fileKey()).isEqualTo(0);
+ assertThat(attrs.isDirectory()).isTrue();
+ assertThat(attrs.isRegularFile()).isFalse();
+ assertThat(attrs.creationTime()).isNotNull();
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/BasicFileAttribute.java b/jimfs/src/test/java/com/google/common/jimfs/BasicFileAttribute.java
new file mode 100644
index 0000000..8311a35
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/BasicFileAttribute.java
@@ -0,0 +1,43 @@
+/*
+ * 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.base.Preconditions.checkNotNull;
+
+import java.nio.file.attribute.FileAttribute;
+
+/** @author Colin Decker */
+public class BasicFileAttribute<T> implements FileAttribute<T> {
+
+ private final String name;
+ private final T value;
+
+ public BasicFileAttribute(String name, T value) {
+ this.name = checkNotNull(name);
+ this.value = checkNotNull(value);
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public T value() {
+ return value;
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java b/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java
new file mode 100644
index 0000000..7428975
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/ByteBufferChannel.java
@@ -0,0 +1,98 @@
+/*
+ * 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 java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SeekableByteChannel;
+
+/** @author Colin Decker */
+public class ByteBufferChannel implements SeekableByteChannel {
+
+ private final ByteBuffer buffer;
+
+ public ByteBufferChannel(byte[] bytes) {
+ this.buffer = ByteBuffer.wrap(bytes);
+ }
+
+ public ByteBufferChannel(byte[] bytes, int offset, int length) {
+ this.buffer = ByteBuffer.wrap(bytes, offset, length);
+ }
+
+ public ByteBufferChannel(int capacity) {
+ this.buffer = ByteBuffer.allocate(capacity);
+ }
+
+ public ByteBufferChannel(ByteBuffer buffer) {
+ this.buffer = buffer;
+ }
+
+ public ByteBuffer buffer() {
+ return buffer;
+ }
+
+ @Override
+ public int read(ByteBuffer dst) throws IOException {
+ if (buffer.remaining() == 0) {
+ return -1;
+ }
+ int length = Math.min(dst.remaining(), buffer.remaining());
+ for (int i = 0; i < length; i++) {
+ dst.put(buffer.get());
+ }
+ return length;
+ }
+
+ @Override
+ public int write(ByteBuffer src) throws IOException {
+ int length = Math.min(src.remaining(), buffer.remaining());
+ for (int i = 0; i < length; i++) {
+ buffer.put(src.get());
+ }
+ return length;
+ }
+
+ @Override
+ public long position() throws IOException {
+ return buffer.position();
+ }
+
+ @Override
+ public SeekableByteChannel position(long newPosition) throws IOException {
+ buffer.position((int) newPosition);
+ return this;
+ }
+
+ @Override
+ public long size() throws IOException {
+ return buffer.limit();
+ }
+
+ @Override
+ public SeekableByteChannel truncate(long size) throws IOException {
+ buffer.limit((int) size);
+ return this;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return true;
+ }
+
+ @Override
+ public void close() throws IOException {}
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java b/jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java
new file mode 100644
index 0000000..671a566
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/ClassLoaderTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2015 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 java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URLClassLoader;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests behavior when user code loads Jimfs in a separate class loader from the system class loader
+ * (which is what {@link FileSystemProvider#installedProviders()} uses to load {@link
+ * FileSystemProvider}s as services from the classpath).
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class ClassLoaderTest {
+
+ @Test
+ public void separateClassLoader() throws Exception {
+ ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
+ ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
+
+ ClassLoader loader = MoreObjects.firstNonNull(contextLoader, systemLoader);
+
+ if (loader instanceof URLClassLoader) {
+ // Anything we can do if it isn't a URLClassLoader?
+ URLClassLoader urlLoader = (URLClassLoader) loader;
+
+ ClassLoader separateLoader =
+ new URLClassLoader(
+ urlLoader.getURLs(), systemLoader.getParent()); // either null or the boostrap loader
+
+ Thread.currentThread().setContextClassLoader(separateLoader);
+ try {
+ Class<?> thisClass = separateLoader.loadClass(getClass().getName());
+ Method createFileSystem = thisClass.getDeclaredMethod("createFileSystem");
+
+ // First, the call to Jimfs.newFileSystem in createFileSystem needs to succeed
+ Object fs = createFileSystem.invoke(null);
+
+ // Next, some sanity checks:
+
+ // The file system is a JimfsFileSystem
+ assertEquals("com.google.common.jimfs.JimfsFileSystem", fs.getClass().getName());
+
+ // But it is not seen as an instance of JimfsFileSystem here because it was loaded by a
+ // different ClassLoader
+ assertFalse(fs instanceof JimfsFileSystem);
+
+ // But it should be an instance of FileSystem regardless, which is the important thing.
+ assertTrue(fs instanceof FileSystem);
+
+ // And normal file operations should work on it despite its provenance from a different
+ // ClassLoader
+ writeAndRead((FileSystem) fs, "bar.txt", "blah blah");
+
+ // And for the heck of it, test the contents of the file that was created in
+ // createFileSystem too
+ assertEquals(
+ "blah", Files.readAllLines(((FileSystem) fs).getPath("foo.txt"), UTF_8).get(0));
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextLoader);
+ }
+ }
+ }
+
+ /**
+ * This method is really just testing that {@code Jimfs.newFileSystem()} succeeds. Without special
+ * handling, when the system class loader loads our {@code FileSystemProvider} implementation as a
+ * service and this code (the user code) is loaded in a separate class loader, the system-loaded
+ * provider won't see the instance of {@code Configuration} we give it as being an instance of the
+ * {@code Configuration} it's expecting (they're completely separate classes) and creation of the
+ * file system will fail.
+ */
+ public static FileSystem createFileSystem() throws IOException {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+
+ // Just some random operations to verify that basic things work on the created file system.
+ writeAndRead(fs, "foo.txt", "blah");
+
+ return fs;
+ }
+
+ private static void writeAndRead(FileSystem fs, String path, String text) throws IOException {
+ Path p = fs.getPath(path);
+ Files.write(p, ImmutableList.of(text), UTF_8);
+ List<String> lines = Files.readAllLines(p, UTF_8);
+ assertEquals(text, lines.get(0));
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java b/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java
new file mode 100644
index 0000000..0404f57
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/ConfigurationTest.java
@@ -0,0 +1,366 @@
+/*
+ * 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.PathNormalization.CASE_FOLD_ASCII;
+import static com.google.common.jimfs.PathNormalization.CASE_FOLD_UNICODE;
+import static com.google.common.jimfs.PathNormalization.NFC;
+import static com.google.common.jimfs.PathNormalization.NFD;
+import static com.google.common.jimfs.PathSubject.paths;
+import static com.google.common.jimfs.WatchServiceConfiguration.polling;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.io.IOException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.PosixFilePermissions;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Configuration}, {@link Configuration.Builder} and file systems created from
+ * them.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class ConfigurationTest {
+
+ private static PathSubject assertThatPath(Path path) {
+ return assert_().about(paths()).that(path);
+ }
+
+ @Test
+ public void testDefaultUnixConfiguration() {
+ Configuration config = Configuration.unix();
+
+ assertThat(config.pathType).isEqualTo(PathType.unix());
+ assertThat(config.roots).containsExactly("/");
+ assertThat(config.workingDirectory).isEqualTo("/work");
+ assertThat(config.nameCanonicalNormalization).isEmpty();
+ assertThat(config.nameDisplayNormalization).isEmpty();
+ assertThat(config.pathEqualityUsesCanonicalForm).isFalse();
+ assertThat(config.blockSize).isEqualTo(8192);
+ assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024);
+ assertThat(config.maxCacheSize).isEqualTo(-1);
+ assertThat(config.attributeViews).containsExactly("basic");
+ assertThat(config.attributeProviders).isEmpty();
+ assertThat(config.defaultAttributeValues).isEmpty();
+ }
+
+ @Test
+ public void testFileSystemForDefaultUnixConfiguration() throws IOException {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+
+ assertThat(fs.getRootDirectories())
+ .containsExactlyElementsIn(ImmutableList.of(fs.getPath("/")))
+ .inOrder();
+ assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("/work"));
+ assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace())
+ .isEqualTo(4L * 1024 * 1024 * 1024);
+ assertThat(fs.supportedFileAttributeViews()).containsExactly("basic");
+
+ Files.createFile(fs.getPath("/foo"));
+ Files.createFile(fs.getPath("/FOO"));
+ }
+
+ @Test
+ public void testDefaultOsXConfiguration() {
+ Configuration config = Configuration.osX();
+
+ assertThat(config.pathType).isEqualTo(PathType.unix());
+ assertThat(config.roots).containsExactly("/");
+ assertThat(config.workingDirectory).isEqualTo("/work");
+ assertThat(config.nameCanonicalNormalization).containsExactly(NFD, CASE_FOLD_ASCII);
+ assertThat(config.nameDisplayNormalization).containsExactly(NFC);
+ assertThat(config.pathEqualityUsesCanonicalForm).isFalse();
+ assertThat(config.blockSize).isEqualTo(8192);
+ assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024);
+ assertThat(config.maxCacheSize).isEqualTo(-1);
+ assertThat(config.attributeViews).containsExactly("basic");
+ assertThat(config.attributeProviders).isEmpty();
+ assertThat(config.defaultAttributeValues).isEmpty();
+ }
+
+ @Test
+ public void testFileSystemForDefaultOsXConfiguration() throws IOException {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.osX());
+
+ assertThat(fs.getRootDirectories())
+ .containsExactlyElementsIn(ImmutableList.of(fs.getPath("/")))
+ .inOrder();
+ assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("/work"));
+ assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace())
+ .isEqualTo(4L * 1024 * 1024 * 1024);
+ assertThat(fs.supportedFileAttributeViews()).containsExactly("basic");
+
+ Files.createFile(fs.getPath("/foo"));
+
+ try {
+ Files.createFile(fs.getPath("/FOO"));
+ fail();
+ } catch (FileAlreadyExistsException expected) {
+ }
+ }
+
+ @Test
+ public void testDefaultWindowsConfiguration() {
+ Configuration config = Configuration.windows();
+
+ assertThat(config.pathType).isEqualTo(PathType.windows());
+ assertThat(config.roots).containsExactly("C:\\");
+ assertThat(config.workingDirectory).isEqualTo("C:\\work");
+ assertThat(config.nameCanonicalNormalization).containsExactly(CASE_FOLD_ASCII);
+ assertThat(config.nameDisplayNormalization).isEmpty();
+ assertThat(config.pathEqualityUsesCanonicalForm).isTrue();
+ assertThat(config.blockSize).isEqualTo(8192);
+ assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024);
+ assertThat(config.maxCacheSize).isEqualTo(-1);
+ assertThat(config.attributeViews).containsExactly("basic");
+ assertThat(config.attributeProviders).isEmpty();
+ assertThat(config.defaultAttributeValues).isEmpty();
+ }
+
+ @Test
+ public void testFileSystemForDefaultWindowsConfiguration() throws IOException {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.windows());
+
+ assertThat(fs.getRootDirectories())
+ .containsExactlyElementsIn(ImmutableList.of(fs.getPath("C:\\")))
+ .inOrder();
+ assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("C:\\work"));
+ assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace())
+ .isEqualTo(4L * 1024 * 1024 * 1024);
+ assertThat(fs.supportedFileAttributeViews()).containsExactly("basic");
+
+ Files.createFile(fs.getPath("C:\\foo"));
+
+ try {
+ Files.createFile(fs.getPath("C:\\FOO"));
+ fail();
+ } catch (FileAlreadyExistsException expected) {
+ }
+ }
+
+ @Test
+ public void testBuilder() {
+ AttributeProvider unixProvider = StandardAttributeProviders.get("unix");
+
+ Configuration config =
+ Configuration.builder(PathType.unix())
+ .setRoots("/")
+ .setWorkingDirectory("/hello/world")
+ .setNameCanonicalNormalization(NFD, CASE_FOLD_UNICODE)
+ .setNameDisplayNormalization(NFC)
+ .setPathEqualityUsesCanonicalForm(true)
+ .setBlockSize(10)
+ .setMaxSize(100)
+ .setMaxCacheSize(50)
+ .setAttributeViews("basic", "posix")
+ .addAttributeProvider(unixProvider)
+ .setDefaultAttributeValue(
+ "posix:permissions", PosixFilePermissions.fromString("---------"))
+ .build();
+
+ assertThat(config.pathType).isEqualTo(PathType.unix());
+ assertThat(config.roots).containsExactly("/");
+ assertThat(config.workingDirectory).isEqualTo("/hello/world");
+ assertThat(config.nameCanonicalNormalization).containsExactly(NFD, CASE_FOLD_UNICODE);
+ assertThat(config.nameDisplayNormalization).containsExactly(NFC);
+ assertThat(config.pathEqualityUsesCanonicalForm).isTrue();
+ assertThat(config.blockSize).isEqualTo(10);
+ assertThat(config.maxSize).isEqualTo(100);
+ assertThat(config.maxCacheSize).isEqualTo(50);
+ assertThat(config.attributeViews).containsExactly("basic", "posix");
+ assertThat(config.attributeProviders).containsExactly(unixProvider);
+ assertThat(config.defaultAttributeValues)
+ .containsEntry("posix:permissions", PosixFilePermissions.fromString("---------"));
+ }
+
+ @Test
+ public void testFileSystemForCustomConfiguration() throws IOException {
+ Configuration config =
+ Configuration.builder(PathType.unix())
+ .setRoots("/")
+ .setWorkingDirectory("/hello/world")
+ .setNameCanonicalNormalization(NFD, CASE_FOLD_UNICODE)
+ .setNameDisplayNormalization(NFC)
+ .setPathEqualityUsesCanonicalForm(true)
+ .setBlockSize(10)
+ .setMaxSize(100)
+ .setMaxCacheSize(50)
+ .setAttributeViews("unix")
+ .setDefaultAttributeValue(
+ "posix:permissions", PosixFilePermissions.fromString("---------"))
+ .build();
+
+ FileSystem fs = Jimfs.newFileSystem(config);
+
+ assertThat(fs.getRootDirectories())
+ .containsExactlyElementsIn(ImmutableList.of(fs.getPath("/")))
+ .inOrder();
+ assertThatPath(fs.getPath("").toRealPath()).isEqualTo(fs.getPath("/hello/world"));
+ assertThat(Iterables.getOnlyElement(fs.getFileStores()).getTotalSpace()).isEqualTo(100);
+ assertThat(fs.supportedFileAttributeViews()).containsExactly("basic", "owner", "posix", "unix");
+
+ Files.createFile(fs.getPath("/foo"));
+ assertThat(Files.getAttribute(fs.getPath("/foo"), "posix:permissions"))
+ .isEqualTo(PosixFilePermissions.fromString("---------"));
+
+ try {
+ Files.createFile(fs.getPath("/FOO"));
+ fail();
+ } catch (FileAlreadyExistsException expected) {
+ }
+ }
+
+ @Test
+ public void testToBuilder() {
+ Configuration config =
+ Configuration.unix().toBuilder()
+ .setWorkingDirectory("/hello/world")
+ .setAttributeViews("basic", "posix")
+ .build();
+
+ assertThat(config.pathType).isEqualTo(PathType.unix());
+ assertThat(config.roots).containsExactly("/");
+ assertThat(config.workingDirectory).isEqualTo("/hello/world");
+ assertThat(config.nameCanonicalNormalization).isEmpty();
+ assertThat(config.nameDisplayNormalization).isEmpty();
+ assertThat(config.pathEqualityUsesCanonicalForm).isFalse();
+ assertThat(config.blockSize).isEqualTo(8192);
+ assertThat(config.maxSize).isEqualTo(4L * 1024 * 1024 * 1024);
+ assertThat(config.maxCacheSize).isEqualTo(-1);
+ assertThat(config.attributeViews).containsExactly("basic", "posix");
+ assertThat(config.attributeProviders).isEmpty();
+ assertThat(config.defaultAttributeValues).isEmpty();
+ }
+
+ @Test
+ public void testSettingRootsUnsupportedByPathType() {
+ assertIllegalRoots(PathType.unix(), "\\");
+ assertIllegalRoots(PathType.unix(), "/", "\\");
+ assertIllegalRoots(PathType.windows(), "/");
+ assertIllegalRoots(PathType.windows(), "C:"); // must have a \ (or a /)
+ }
+
+ private static void assertIllegalRoots(PathType type, String first, String... more) {
+ try {
+ Configuration.builder(type).setRoots(first, more); // wrong root
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSettingWorkingDirectoryWithRelativePath() {
+ try {
+ Configuration.unix().toBuilder().setWorkingDirectory("foo/bar");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ Configuration.windows().toBuilder().setWorkingDirectory("foo\\bar");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSettingNormalizationWhenNormalizationAlreadySet() {
+ assertIllegalNormalizations(NFC, NFC);
+ assertIllegalNormalizations(NFC, NFD);
+ assertIllegalNormalizations(CASE_FOLD_ASCII, CASE_FOLD_ASCII);
+ assertIllegalNormalizations(CASE_FOLD_ASCII, CASE_FOLD_UNICODE);
+ }
+
+ private static void assertIllegalNormalizations(
+ PathNormalization first, PathNormalization... more) {
+ try {
+ Configuration.builder(PathType.unix()).setNameCanonicalNormalization(first, more);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ Configuration.builder(PathType.unix()).setNameDisplayNormalization(first, more);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testSetDefaultAttributeValue_illegalAttributeFormat() {
+ try {
+ Configuration.unix().toBuilder().setDefaultAttributeValue("foo", 1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test // how's that for a name?
+ public void testCreateFileSystemFromConfigurationWithWorkingDirectoryNotUnderConfiguredRoot() {
+ try {
+ Jimfs.newFileSystem(
+ Configuration.windows().toBuilder()
+ .setRoots("C:\\", "D:\\")
+ .setWorkingDirectory("E:\\foo")
+ .build());
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testFileSystemWithDefaultWatchService() throws IOException {
+ FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+
+ WatchService watchService = fs.newWatchService();
+ assertThat(watchService).isInstanceOf(PollingWatchService.class);
+
+ PollingWatchService pollingWatchService = (PollingWatchService) watchService;
+ assertThat(pollingWatchService.interval).isEqualTo(5);
+ assertThat(pollingWatchService.timeUnit).isEqualTo(SECONDS);
+ }
+
+ @Test
+ public void testFileSystemWithCustomWatchServicePollingInterval() throws IOException {
+ FileSystem fs =
+ Jimfs.newFileSystem(
+ Configuration.unix().toBuilder()
+ .setWatchServiceConfiguration(polling(10, MILLISECONDS))
+ .build());
+
+ WatchService watchService = fs.newWatchService();
+ assertThat(watchService).isInstanceOf(PollingWatchService.class);
+
+ PollingWatchService pollingWatchService = (PollingWatchService) watchService;
+ assertThat(pollingWatchService.interval).isEqualTo(10);
+ assertThat(pollingWatchService.timeUnit).isEqualTo(MILLISECONDS);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java b/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java
new file mode 100644
index 0000000..217509d
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/DirectoryTest.java
@@ -0,0 +1,383 @@
+/*
+ * 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.Name.PARENT;
+import static com.google.common.jimfs.Name.SELF;
+import static com.google.common.jimfs.TestUtils.regularFile;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
+import java.util.HashSet;
+import java.util.Set;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Directory}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class DirectoryTest {
+
+ private Directory root;
+ private Directory dir;
+
+ @Before
+ public void setUp() {
+ root = Directory.createRoot(0, Name.simple("/"));
+
+ dir = Directory.create(1);
+ root.link(Name.simple("foo"), dir);
+ }
+
+ @Test
+ public void testRootDirectory() {
+ assertThat(root.entryCount()).isEqualTo(3); // two for parent/self, one for dir
+ assertThat(root.isEmpty()).isFalse();
+ assertThat(root.entryInParent()).isEqualTo(entry(root, "/", root));
+ assertThat(root.entryInParent().name()).isEqualTo(Name.simple("/"));
+
+ assertParentAndSelf(root, root, root);
+ }
+
+ @Test
+ public void testEmptyDirectory() {
+ assertThat(dir.entryCount()).isEqualTo(2);
+ assertThat(dir.isEmpty()).isTrue();
+
+ assertParentAndSelf(dir, root, dir);
+ }
+
+ @Test
+ public void testGet() {
+ assertThat(root.get(Name.simple("foo"))).isEqualTo(entry(root, "foo", dir));
+ assertThat(dir.get(Name.simple("foo"))).isNull();
+ assertThat(root.get(Name.simple("Foo"))).isNull();
+ }
+
+ @Test
+ public void testLink() {
+ assertThat(dir.get(Name.simple("bar"))).isNull();
+
+ File bar = Directory.create(2);
+ dir.link(Name.simple("bar"), bar);
+
+ assertThat(dir.get(Name.simple("bar"))).isEqualTo(entry(dir, "bar", bar));
+ }
+
+ @Test
+ public void testLink_existingNameFails() {
+ try {
+ root.link(Name.simple("foo"), Directory.create(2));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testLink_parentAndSelfNameFails() {
+ try {
+ dir.link(Name.simple("."), Directory.create(2));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ dir.link(Name.simple(".."), Directory.create(2));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testGet_normalizingCaseInsensitive() {
+ File bar = Directory.create(2);
+ Name barName = caseInsensitive("bar");
+
+ dir.link(barName, bar);
+
+ DirectoryEntry expected = new DirectoryEntry(dir, barName, bar);
+ assertThat(dir.get(caseInsensitive("bar"))).isEqualTo(expected);
+ assertThat(dir.get(caseInsensitive("BAR"))).isEqualTo(expected);
+ assertThat(dir.get(caseInsensitive("Bar"))).isEqualTo(expected);
+ assertThat(dir.get(caseInsensitive("baR"))).isEqualTo(expected);
+ }
+
+ @Test
+ public void testUnlink() {
+ assertThat(root.get(Name.simple("foo"))).isNotNull();
+
+ root.unlink(Name.simple("foo"));
+
+ assertThat(root.get(Name.simple("foo"))).isNull();
+ }
+
+ @Test
+ public void testUnlink_nonExistentNameFails() {
+ try {
+ dir.unlink(Name.simple("bar"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testUnlink_parentAndSelfNameFails() {
+ try {
+ dir.unlink(Name.simple("."));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ dir.unlink(Name.simple(".."));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testUnlink_normalizingCaseInsensitive() {
+ dir.link(caseInsensitive("bar"), Directory.create(2));
+
+ assertThat(dir.get(caseInsensitive("bar"))).isNotNull();
+
+ dir.unlink(caseInsensitive("BAR"));
+
+ assertThat(dir.get(caseInsensitive("bar"))).isNull();
+ }
+
+ @Test
+ public void testLinkDirectory() {
+ Directory newDir = Directory.create(10);
+
+ assertThat(newDir.entryInParent()).isNull();
+ assertThat(newDir.get(Name.SELF).file()).isEqualTo(newDir);
+ assertThat(newDir.get(Name.PARENT)).isNull();
+ assertThat(newDir.links()).isEqualTo(1);
+
+ dir.link(Name.simple("foo"), newDir);
+
+ assertThat(newDir.entryInParent()).isEqualTo(entry(dir, "foo", newDir));
+ assertThat(newDir.parent()).isEqualTo(dir);
+ assertThat(newDir.entryInParent().name()).isEqualTo(Name.simple("foo"));
+ assertThat(newDir.get(Name.SELF)).isEqualTo(entry(newDir, ".", newDir));
+ assertThat(newDir.get(Name.PARENT)).isEqualTo(entry(newDir, "..", dir));
+ assertThat(newDir.links()).isEqualTo(2);
+ }
+
+ @Test
+ public void testUnlinkDirectory() {
+ Directory newDir = Directory.create(10);
+
+ dir.link(Name.simple("foo"), newDir);
+
+ assertThat(dir.links()).isEqualTo(3);
+
+ assertThat(newDir.entryInParent()).isEqualTo(entry(dir, "foo", newDir));
+ assertThat(newDir.links()).isEqualTo(2);
+
+ dir.unlink(Name.simple("foo"));
+
+ assertThat(dir.links()).isEqualTo(2);
+
+ assertThat(newDir.entryInParent()).isEqualTo(entry(dir, "foo", newDir));
+ assertThat(newDir.get(Name.SELF).file()).isEqualTo(newDir);
+ assertThat(newDir.get(Name.PARENT)).isEqualTo(entry(newDir, "..", dir));
+ assertThat(newDir.links()).isEqualTo(1);
+ }
+
+ @Test
+ public void testSnapshot() {
+ root.link(Name.simple("bar"), regularFile(10));
+ root.link(Name.simple("abc"), regularFile(10));
+
+ /*
+ * If we inline this into the assertThat call below, javac resolves it to assertThat(SortedSet),
+ * which isn't available publicly. Our @GoogleInternal checks consider that to be an error, even
+ * though the code will compile fine externally by resolving to assertThat(Iterable) instead. So
+ * we avoid that by assigning to a non-SortedSet type here.
+ */
+ ImmutableSet<Name> snapshot = root.snapshot();
+ // does not include . or .. and is sorted by the name
+ assertThat(snapshot)
+ .containsExactly(Name.simple("abc"), Name.simple("bar"), Name.simple("foo"))
+ .inOrder();
+ }
+
+ @Test
+ public void testSnapshot_sortsUsingStringAndNotCanonicalValueOfNames() {
+ dir.link(caseInsensitive("FOO"), regularFile(10));
+ dir.link(caseInsensitive("bar"), regularFile(10));
+
+ ImmutableSortedSet<Name> snapshot = dir.snapshot();
+ Iterable<String> strings = Iterables.transform(snapshot, Functions.toStringFunction());
+
+ // "FOO" comes before "bar"
+ // if the order were based on the normalized, canonical form of the names ("foo" and "bar"),
+ // "bar" would come first
+ assertThat(strings).containsExactly("FOO", "bar").inOrder();
+ }
+
+ // Tests for internal hash table implementation
+
+ private static final Directory A = Directory.create(0);
+
+ @Test
+ public void testInitialState() {
+ assertThat(dir.entryCount()).isEqualTo(2);
+ assertThat(ImmutableSet.copyOf(dir))
+ .containsExactly(
+ new DirectoryEntry(dir, Name.SELF, dir), new DirectoryEntry(dir, Name.PARENT, root));
+ assertThat(dir.get(Name.simple("foo"))).isNull();
+ }
+
+ @Test
+ public void testPutAndGet() {
+ dir.put(entry("foo"));
+
+ assertThat(dir.entryCount()).isEqualTo(3);
+ assertThat(ImmutableSet.copyOf(dir)).contains(entry("foo"));
+ assertThat(dir.get(Name.simple("foo"))).isEqualTo(entry("foo"));
+
+ dir.put(entry("bar"));
+
+ assertThat(dir.entryCount()).isEqualTo(4);
+ assertThat(ImmutableSet.copyOf(dir)).containsAtLeast(entry("foo"), entry("bar"));
+ assertThat(dir.get(Name.simple("foo"))).isEqualTo(entry("foo"));
+ assertThat(dir.get(Name.simple("bar"))).isEqualTo(entry("bar"));
+ }
+
+ @Test
+ public void testPutEntryForExistingNameIsIllegal() {
+ dir.put(entry("foo"));
+
+ try {
+ dir.put(entry("foo"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testRemove() {
+ dir.put(entry("foo"));
+ dir.put(entry("bar"));
+
+ dir.remove(Name.simple("foo"));
+
+ assertThat(dir.entryCount()).isEqualTo(3);
+ assertThat(ImmutableSet.copyOf(dir))
+ .containsExactly(
+ entry("bar"),
+ new DirectoryEntry(dir, Name.SELF, dir),
+ new DirectoryEntry(dir, Name.PARENT, root));
+ assertThat(dir.get(Name.simple("foo"))).isNull();
+ assertThat(dir.get(Name.simple("bar"))).isEqualTo(entry("bar"));
+
+ dir.remove(Name.simple("bar"));
+
+ assertThat(dir.entryCount()).isEqualTo(2);
+
+ dir.put(entry("bar"));
+ dir.put(entry("foo")); // these should just succeeded
+ }
+
+ @Test
+ public void testManyPutsAndRemoves() {
+ // test resizing/rehashing
+
+ Set<DirectoryEntry> entriesInDir = new HashSet<>();
+ entriesInDir.add(new DirectoryEntry(dir, Name.SELF, dir));
+ entriesInDir.add(new DirectoryEntry(dir, Name.PARENT, root));
+
+ // add 1000 entries
+ for (int i = 0; i < 1000; i++) {
+ DirectoryEntry entry = entry(String.valueOf(i));
+ dir.put(entry);
+ entriesInDir.add(entry);
+
+ assertThat(ImmutableSet.copyOf(dir)).isEqualTo(entriesInDir);
+
+ for (DirectoryEntry expected : entriesInDir) {
+ assertThat(dir.get(expected.name())).isEqualTo(expected);
+ }
+ }
+
+ // remove 1000 entries
+ for (int i = 0; i < 1000; i++) {
+ dir.remove(Name.simple(String.valueOf(i)));
+ entriesInDir.remove(entry(String.valueOf(i)));
+
+ assertThat(ImmutableSet.copyOf(dir)).isEqualTo(entriesInDir);
+
+ for (DirectoryEntry expected : entriesInDir) {
+ assertThat(dir.get(expected.name())).isEqualTo(expected);
+ }
+ }
+
+ // mixed adds and removes
+ for (int i = 0; i < 10000; i++) {
+ DirectoryEntry entry = entry(String.valueOf(i));
+ dir.put(entry);
+ entriesInDir.add(entry);
+
+ if (i > 0 && i % 20 == 0) {
+ String nameToRemove = String.valueOf(i / 2);
+ dir.remove(Name.simple(nameToRemove));
+ entriesInDir.remove(entry(nameToRemove));
+ }
+ }
+
+ // for this one, only test that the end result is correct
+ // takes too long to test at each iteration
+ assertThat(ImmutableSet.copyOf(dir)).isEqualTo(entriesInDir);
+
+ for (DirectoryEntry expected : entriesInDir) {
+ assertThat(dir.get(expected.name())).isEqualTo(expected);
+ }
+ }
+
+ private static DirectoryEntry entry(String name) {
+ return new DirectoryEntry(A, Name.simple(name), A);
+ }
+
+ private static DirectoryEntry entry(Directory dir, String name, @NullableDecl File file) {
+ return new DirectoryEntry(dir, Name.simple(name), file);
+ }
+
+ private static void assertParentAndSelf(Directory dir, File parent, File self) {
+ assertThat(dir).isEqualTo(self);
+ assertThat(dir.parent()).isEqualTo(parent);
+
+ assertThat(dir.get(PARENT)).isEqualTo(entry((Directory) self, "..", parent));
+ assertThat(dir.get(SELF)).isEqualTo(entry((Directory) self, ".", self));
+ }
+
+ private static Name caseInsensitive(String name) {
+ return Name.create(name, PathNormalization.CASE_FOLD_UNICODE.apply(name));
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/DosAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/DosAttributeProviderTest.java
new file mode 100644
index 0000000..2781263
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/DosAttributeProviderTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.attribute.DosFileAttributeView;
+import java.nio.file.attribute.DosFileAttributes;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link DosAttributeProvider}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class DosAttributeProviderTest extends AbstractAttributeProviderTest<DosAttributeProvider> {
+
+ private static final ImmutableList<String> DOS_ATTRIBUTES =
+ ImmutableList.of("hidden", "archive", "readonly", "system");
+
+ @Override
+ protected DosAttributeProvider createProvider() {
+ return new DosAttributeProvider();
+ }
+
+ @Override
+ protected Set<? extends AttributeProvider> createInheritedProviders() {
+ return ImmutableSet.of(new BasicAttributeProvider(), new OwnerAttributeProvider());
+ }
+
+ @Test
+ public void testInitialAttributes() {
+ for (String attribute : DOS_ATTRIBUTES) {
+ assertThat(provider.get(file, attribute)).isEqualTo(false);
+ }
+ }
+
+ @Test
+ public void testSet() {
+ for (String attribute : DOS_ATTRIBUTES) {
+ assertSetAndGetSucceeds(attribute, true);
+ assertSetFailsOnCreate(attribute, true);
+ }
+ }
+
+ @Test
+ public void testView() throws IOException {
+ DosFileAttributeView view =
+ provider.view(
+ fileLookup(),
+ ImmutableMap.<String, FileAttributeView>of(
+ "basic", new BasicAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS)));
+ assertNotNull(view);
+
+ assertThat(view.name()).isEqualTo("dos");
+
+ DosFileAttributes attrs = view.readAttributes();
+ assertThat(attrs.isHidden()).isFalse();
+ assertThat(attrs.isArchive()).isFalse();
+ assertThat(attrs.isReadOnly()).isFalse();
+ assertThat(attrs.isSystem()).isFalse();
+
+ view.setArchive(true);
+ view.setReadOnly(true);
+ view.setHidden(true);
+ view.setSystem(false);
+
+ assertThat(attrs.isHidden()).isFalse();
+ assertThat(attrs.isArchive()).isFalse();
+ assertThat(attrs.isReadOnly()).isFalse();
+
+ attrs = view.readAttributes();
+ assertThat(attrs.isHidden()).isTrue();
+ assertThat(attrs.isArchive()).isTrue();
+ assertThat(attrs.isReadOnly()).isTrue();
+ assertThat(attrs.isSystem()).isFalse();
+
+ view.setTimes(FileTime.fromMillis(0L), null, null);
+ assertThat(view.readAttributes().lastModifiedTime()).isEqualTo(FileTime.fromMillis(0L));
+ }
+
+ @Test
+ public void testAttributes() {
+ DosFileAttributes attrs = provider.readAttributes(file);
+ assertThat(attrs.isHidden()).isFalse();
+ assertThat(attrs.isArchive()).isFalse();
+ assertThat(attrs.isReadOnly()).isFalse();
+ assertThat(attrs.isSystem()).isFalse();
+
+ file.setAttribute("dos", "hidden", true);
+
+ attrs = provider.readAttributes(file);
+ assertThat(attrs.isHidden()).isTrue();
+ assertThat(attrs.isArchive()).isFalse();
+ assertThat(attrs.isReadOnly()).isFalse();
+ assertThat(attrs.isSystem()).isFalse();
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java
new file mode 100644
index 0000000..9e3cb40
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/FileFactoryTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.truth.Truth.assertThat;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link FileFactory}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class FileFactoryTest {
+
+ private FileFactory factory;
+
+ @Before
+ public void setUp() {
+ factory = new FileFactory(new HeapDisk(2, 2, 0));
+ }
+
+ @Test
+ public void testCreateFiles_basic() {
+ File file = factory.createDirectory();
+ assertThat(file.id()).isEqualTo(0L);
+ assertThat(file.isDirectory()).isTrue();
+
+ file = factory.createRegularFile();
+ assertThat(file.id()).isEqualTo(1L);
+ assertThat(file.isRegularFile()).isTrue();
+
+ file = factory.createSymbolicLink(fakePath());
+ assertThat(file.id()).isEqualTo(2L);
+ assertThat(file.isSymbolicLink()).isTrue();
+ }
+
+ @Test
+ public void testCreateFiles_withSupplier() {
+ File file = factory.directoryCreator().get();
+ assertThat(file.id()).isEqualTo(0L);
+ assertThat(file.isDirectory()).isTrue();
+
+ file = factory.regularFileCreator().get();
+ assertThat(file.id()).isEqualTo(1L);
+ assertThat(file.isRegularFile()).isTrue();
+
+ file = factory.symbolicLinkCreator(fakePath()).get();
+ assertThat(file.id()).isEqualTo(2L);
+ assertThat(file.isSymbolicLink()).isTrue();
+ }
+
+ static JimfsPath fakePath() {
+ return PathServiceTest.fakeUnixPathService().emptyPath();
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java
new file mode 100644
index 0000000..f8143b6
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/FileSystemStateTest.java
@@ -0,0 +1,178 @@
+/*
+ * 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 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 java.io.Closeable;
+import java.io.IOException;
+import java.nio.file.ClosedFileSystemException;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link FileSystemState}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class FileSystemStateTest {
+
+ private final TestRunnable onClose = new TestRunnable();
+ private final FileSystemState state = new FileSystemState(onClose);
+
+ @Test
+ public void testIsOpen() throws IOException {
+ assertTrue(state.isOpen());
+ state.close();
+ assertFalse(state.isOpen());
+ }
+
+ @Test
+ public void testCheckOpen() throws IOException {
+ state.checkOpen(); // does not throw
+ state.close();
+ try {
+ state.checkOpen();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+ }
+
+ @Test
+ public void testClose_callsOnCloseRunnable() throws IOException {
+ assertEquals(0, onClose.runCount);
+ state.close();
+ assertEquals(1, onClose.runCount);
+ }
+
+ @Test
+ public void testClose_multipleTimesDoNothing() throws IOException {
+ state.close();
+ assertEquals(1, onClose.runCount);
+ state.close();
+ state.close();
+ assertEquals(1, onClose.runCount);
+ }
+
+ @Test
+ public void testClose_registeredResourceIsClosed() throws IOException {
+ TestCloseable resource = new TestCloseable();
+ state.register(resource);
+ assertFalse(resource.closed);
+ state.close();
+ assertTrue(resource.closed);
+ }
+
+ @Test
+ public void testClose_unregisteredResourceIsNotClosed() throws IOException {
+ TestCloseable resource = new TestCloseable();
+ state.register(resource);
+ assertFalse(resource.closed);
+ state.unregister(resource);
+ state.close();
+ assertFalse(resource.closed);
+ }
+
+ @Test
+ public void testClose_multipleRegisteredResourcesAreClosed() throws IOException {
+ List<TestCloseable> resources =
+ ImmutableList.of(new TestCloseable(), new TestCloseable(), new TestCloseable());
+ for (TestCloseable resource : resources) {
+ state.register(resource);
+ assertFalse(resource.closed);
+ }
+ state.close();
+ for (TestCloseable resource : resources) {
+ assertTrue(resource.closed);
+ }
+ }
+
+ @Test
+ public void testClose_resourcesThatThrowOnClose() {
+ List<TestCloseable> resources =
+ ImmutableList.of(
+ new TestCloseable(),
+ new ThrowsOnClose("a"),
+ new TestCloseable(),
+ new ThrowsOnClose("b"),
+ new ThrowsOnClose("c"),
+ new TestCloseable(),
+ new TestCloseable());
+ for (TestCloseable resource : resources) {
+ state.register(resource);
+ assertFalse(resource.closed);
+ }
+
+ try {
+ state.close();
+ fail();
+ } catch (IOException expected) {
+ Throwable[] suppressed = expected.getSuppressed();
+ assertEquals(2, suppressed.length);
+ ImmutableSet<String> messages =
+ ImmutableSet.of(
+ expected.getMessage(), suppressed[0].getMessage(), suppressed[1].getMessage());
+ assertEquals(ImmutableSet.of("a", "b", "c"), messages);
+ }
+
+ for (TestCloseable resource : resources) {
+ assertTrue(resource.closed);
+ }
+ }
+
+ private static class TestCloseable implements Closeable {
+
+ boolean closed = false;
+
+ @Override
+ public void close() throws IOException {
+ closed = true;
+ }
+ }
+
+ private static final class TestRunnable implements Runnable {
+ int runCount = 0;
+
+ @Override
+ public void run() {
+ runCount++;
+ }
+ }
+
+ private static class ThrowsOnClose extends TestCloseable {
+
+ private final String string;
+
+ private ThrowsOnClose(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ throw new IOException(string);
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileTest.java
new file mode 100644
index 0000000..83cda00
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/FileTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.FileFactoryTest.fakePath;
+import static com.google.common.jimfs.TestUtils.regularFile;
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link File}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class FileTest {
+
+ @Test
+ public void testAttributes() {
+ // these methods are basically just thin wrappers around a map, so no need to test too
+ // thoroughly
+
+ File file = RegularFile.create(0, new HeapDisk(10, 10, 10));
+
+ assertThat(file.getAttributeKeys()).isEmpty();
+ assertThat(file.getAttribute("foo", "foo")).isNull();
+
+ file.deleteAttribute("foo", "foo"); // doesn't throw
+
+ file.setAttribute("foo", "foo", "foo");
+
+ assertThat(file.getAttributeKeys()).containsExactly("foo:foo");
+ assertThat(file.getAttribute("foo", "foo")).isEqualTo("foo");
+
+ file.deleteAttribute("foo", "foo");
+
+ assertThat(file.getAttributeKeys()).isEmpty();
+ assertThat(file.getAttribute("foo", "foo")).isNull();
+ }
+
+ @Test
+ public void testFileBasics() {
+ File file = regularFile(0);
+
+ assertThat(file.id()).isEqualTo(0);
+ assertThat(file.links()).isEqualTo(0);
+ }
+
+ @Test
+ public void testDirectory() {
+ File file = Directory.create(0);
+ assertThat(file.isDirectory()).isTrue();
+ assertThat(file.isRegularFile()).isFalse();
+ assertThat(file.isSymbolicLink()).isFalse();
+ }
+
+ @Test
+ public void testRegularFile() {
+ File file = regularFile(10);
+ assertThat(file.isDirectory()).isFalse();
+ assertThat(file.isRegularFile()).isTrue();
+ assertThat(file.isSymbolicLink()).isFalse();
+ }
+
+ @Test
+ public void testSymbolicLink() {
+ File file = SymbolicLink.create(0, fakePath());
+ assertThat(file.isDirectory()).isFalse();
+ assertThat(file.isRegularFile()).isFalse();
+ assertThat(file.isSymbolicLink()).isTrue();
+ }
+
+ @Test
+ public void testRootDirectory() {
+ Directory file = Directory.createRoot(0, Name.simple("/"));
+ assertThat(file.isRootDirectory()).isTrue();
+
+ Directory otherFile = Directory.createRoot(1, Name.simple("$"));
+ assertThat(otherFile.isRootDirectory()).isTrue();
+ }
+
+ @Test
+ public void testLinkAndUnlink() {
+ File file = regularFile(0);
+ assertThat(file.links()).isEqualTo(0);
+
+ file.incrementLinkCount();
+ assertThat(file.links()).isEqualTo(1);
+
+ file.incrementLinkCount();
+ assertThat(file.links()).isEqualTo(2);
+
+ file.decrementLinkCount();
+ assertThat(file.links()).isEqualTo(1);
+
+ file.decrementLinkCount();
+ assertThat(file.links()).isEqualTo(0);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java b/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java
new file mode 100644
index 0000000..54f590d
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/FileTreeTest.java
@@ -0,0 +1,463 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+import static com.google.common.jimfs.TestUtils.regularFile;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
+import static org.junit.Assert.fail;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.base.Strings;
+import java.io.IOException;
+import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link FileTree}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class FileTreeTest {
+
+ /*
+ * Directory structure. Each file should have a unique name.
+ *
+ * /
+ * work/
+ * one/
+ * two/
+ * three/
+ * eleven
+ * four/
+ * five -> /foo
+ * six -> ../one
+ * loop -> ../four/loop
+ * foo/
+ * bar/
+ * $
+ * a/
+ * b/
+ * c/
+ */
+
+ /**
+ * This path service is for unix-like paths, with the exception that it recognizes $ and ! as
+ * roots in addition to /, allowing for up to three roots. When creating a {@linkplain
+ * PathType#toUriPath URI path}, we prefix the path with / to differentiate between a path like
+ * "$foo/bar" and one like "/$foo/bar". They would become "/$foo/bar" and "//$foo/bar"
+ * respectively.
+ */
+ private final PathService pathService =
+ PathServiceTest.fakePathService(
+ new PathType(true, '/') {
+ @Override
+ public ParseResult parsePath(String path) {
+ String root = null;
+ if (path.matches("^[/$!].*")) {
+ root = path.substring(0, 1);
+ path = path.substring(1);
+ }
+ return new ParseResult(root, Splitter.on('/').omitEmptyStrings().split(path));
+ }
+
+ @Override
+ public String toString(@NullableDecl String root, Iterable<String> names) {
+ root = Strings.nullToEmpty(root);
+ return root + Joiner.on('/').join(names);
+ }
+
+ @Override
+ public String toUriPath(String root, Iterable<String> names, boolean directory) {
+ // need to add extra / to differentiate between paths "/$foo/bar" and "$foo/bar".
+ return "/" + toString(root, names);
+ }
+
+ @Override
+ public ParseResult parseUriPath(String uriPath) {
+ checkArgument(
+ uriPath.matches("^/[/$!].*"), "uriPath (%s) must start with // or /$ or /!");
+ return parsePath(uriPath.substring(1)); // skip leading /
+ }
+ },
+ false);
+
+ private FileTree fileTree;
+ private File workingDirectory;
+ private final Map<String, File> files = new HashMap<>();
+
+ @Before
+ public void setUp() {
+ Directory root = Directory.createRoot(0, Name.simple("/"));
+ files.put("/", root);
+
+ Directory otherRoot = Directory.createRoot(2, Name.simple("$"));
+ files.put("$", otherRoot);
+
+ Map<Name, Directory> roots = new HashMap<>();
+ roots.put(Name.simple("/"), root);
+ roots.put(Name.simple("$"), otherRoot);
+
+ fileTree = new FileTree(roots);
+
+ workingDirectory = createDirectory("/", "work");
+
+ createDirectory("work", "one");
+ createDirectory("one", "two");
+ createFile("one", "eleven");
+ createDirectory("two", "three");
+ createDirectory("work", "four");
+ createSymbolicLink("four", "five", "/foo");
+ createSymbolicLink("four", "six", "../one");
+ createSymbolicLink("four", "loop", "../four/loop");
+ createDirectory("/", "foo");
+ createDirectory("foo", "bar");
+ createDirectory("$", "a");
+ createDirectory("a", "b");
+ createDirectory("b", "c");
+ }
+
+ // absolute lookups
+
+ @Test
+ public void testLookup_root() throws IOException {
+ assertExists(lookup("/"), "/", "/");
+ assertExists(lookup("$"), "$", "$");
+ }
+
+ @Test
+ public void testLookup_nonExistentRoot() throws IOException {
+ try {
+ lookup("!");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+
+ try {
+ lookup("!a");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+ }
+
+ @Test
+ public void testLookup_absolute() throws IOException {
+ assertExists(lookup("/work"), "/", "work");
+ assertExists(lookup("/work/one/two/three"), "two", "three");
+ assertExists(lookup("$a"), "$", "a");
+ assertExists(lookup("$a/b/c"), "b", "c");
+ }
+
+ @Test
+ public void testLookup_absolute_notExists() throws IOException {
+ try {
+ lookup("/a/b");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+
+ try {
+ lookup("/work/one/foo/bar");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+
+ try {
+ lookup("$c/d");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+
+ try {
+ lookup("$a/b/c/d/e");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+ }
+
+ @Test
+ public void testLookup_absolute_parentExists() throws IOException {
+ assertParentExists(lookup("/a"), "/");
+ assertParentExists(lookup("/foo/baz"), "foo");
+ assertParentExists(lookup("$c"), "$");
+ assertParentExists(lookup("$a/b/c/d"), "c");
+ }
+
+ @Test
+ public void testLookup_absolute_nonDirectoryIntermediateFile() throws IOException {
+ try {
+ lookup("/work/one/eleven/twelve");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+
+ try {
+ lookup("/work/one/eleven/twelve/thirteen/fourteen");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+ }
+
+ @Test
+ public void testLookup_absolute_intermediateSymlink() throws IOException {
+ assertExists(lookup("/work/four/five/bar"), "foo", "bar");
+ assertExists(lookup("/work/four/six/two/three"), "two", "three");
+
+ // NOFOLLOW_LINKS doesn't affect intermediate symlinks
+ assertExists(lookup("/work/four/five/bar", NOFOLLOW_LINKS), "foo", "bar");
+ assertExists(lookup("/work/four/six/two/three", NOFOLLOW_LINKS), "two", "three");
+ }
+
+ @Test
+ public void testLookup_absolute_intermediateSymlink_parentExists() throws IOException {
+ assertParentExists(lookup("/work/four/five/baz"), "foo");
+ assertParentExists(lookup("/work/four/six/baz"), "one");
+ }
+
+ @Test
+ public void testLookup_absolute_finalSymlink() throws IOException {
+ assertExists(lookup("/work/four/five"), "/", "foo");
+ assertExists(lookup("/work/four/six"), "work", "one");
+ }
+
+ @Test
+ public void testLookup_absolute_finalSymlink_nofollowLinks() throws IOException {
+ assertExists(lookup("/work/four/five", NOFOLLOW_LINKS), "four", "five");
+ assertExists(lookup("/work/four/six", NOFOLLOW_LINKS), "four", "six");
+ assertExists(lookup("/work/four/loop", NOFOLLOW_LINKS), "four", "loop");
+ }
+
+ @Test
+ public void testLookup_absolute_symlinkLoop() {
+ try {
+ lookup("/work/four/loop");
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ lookup("/work/four/loop/whatever");
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testLookup_absolute_withDotsInPath() throws IOException {
+ assertExists(lookup("/."), "/", "/");
+ assertExists(lookup("/./././."), "/", "/");
+ assertExists(lookup("/work/./one/./././two/three"), "two", "three");
+ assertExists(lookup("/work/./one/./././two/././three"), "two", "three");
+ assertExists(lookup("/work/./one/./././two/three/././."), "two", "three");
+ }
+
+ @Test
+ public void testLookup_absolute_withDotDotsInPath() throws IOException {
+ assertExists(lookup("/.."), "/", "/");
+ assertExists(lookup("/../../.."), "/", "/");
+ assertExists(lookup("/work/.."), "/", "/");
+ assertExists(lookup("/work/../work/one/two/../two/three"), "two", "three");
+ assertExists(lookup("/work/one/two/../../four/../one/two/three/../three"), "two", "three");
+ assertExists(lookup("/work/one/two/three/../../two/three/.."), "one", "two");
+ assertExists(lookup("/work/one/two/three/../../two/three/../.."), "work", "one");
+ }
+
+ @Test
+ public void testLookup_absolute_withDotDotsInPath_afterSymlink() throws IOException {
+ assertExists(lookup("/work/four/five/.."), "/", "/");
+ assertExists(lookup("/work/four/six/.."), "/", "work");
+ }
+
+ // relative lookups
+
+ @Test
+ public void testLookup_relative() throws IOException {
+ assertExists(lookup("one"), "work", "one");
+ assertExists(lookup("one/two/three"), "two", "three");
+ }
+
+ @Test
+ public void testLookup_relative_notExists() throws IOException {
+ try {
+ lookup("a/b");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+
+ try {
+ lookup("one/foo/bar");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+ }
+
+ @Test
+ public void testLookup_relative_parentExists() throws IOException {
+ assertParentExists(lookup("a"), "work");
+ assertParentExists(lookup("one/two/four"), "two");
+ }
+
+ @Test
+ public void testLookup_relative_nonDirectoryIntermediateFile() throws IOException {
+ try {
+ lookup("one/eleven/twelve");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+
+ try {
+ lookup("one/eleven/twelve/thirteen/fourteen");
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+ }
+
+ @Test
+ public void testLookup_relative_intermediateSymlink() throws IOException {
+ assertExists(lookup("four/five/bar"), "foo", "bar");
+ assertExists(lookup("four/six/two/three"), "two", "three");
+
+ // NOFOLLOW_LINKS doesn't affect intermediate symlinks
+ assertExists(lookup("four/five/bar", NOFOLLOW_LINKS), "foo", "bar");
+ assertExists(lookup("four/six/two/three", NOFOLLOW_LINKS), "two", "three");
+ }
+
+ @Test
+ public void testLookup_relative_intermediateSymlink_parentExists() throws IOException {
+ assertParentExists(lookup("four/five/baz"), "foo");
+ assertParentExists(lookup("four/six/baz"), "one");
+ }
+
+ @Test
+ public void testLookup_relative_finalSymlink() throws IOException {
+ assertExists(lookup("four/five"), "/", "foo");
+ assertExists(lookup("four/six"), "work", "one");
+ }
+
+ @Test
+ public void testLookup_relative_finalSymlink_nofollowLinks() throws IOException {
+ assertExists(lookup("four/five", NOFOLLOW_LINKS), "four", "five");
+ assertExists(lookup("four/six", NOFOLLOW_LINKS), "four", "six");
+ assertExists(lookup("four/loop", NOFOLLOW_LINKS), "four", "loop");
+ }
+
+ @Test
+ public void testLookup_relative_symlinkLoop() {
+ try {
+ lookup("four/loop");
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ lookup("four/loop/whatever");
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testLookup_relative_emptyPath() throws IOException {
+ assertExists(lookup(""), "/", "work");
+ }
+
+ @Test
+ public void testLookup_relative_withDotsInPath() throws IOException {
+ assertExists(lookup("."), "/", "work");
+ assertExists(lookup("././."), "/", "work");
+ assertExists(lookup("./one/./././two/three"), "two", "three");
+ assertExists(lookup("./one/./././two/././three"), "two", "three");
+ assertExists(lookup("./one/./././two/three/././."), "two", "three");
+ }
+
+ @Test
+ public void testLookup_relative_withDotDotsInPath() throws IOException {
+ assertExists(lookup(".."), "/", "/");
+ assertExists(lookup("../../.."), "/", "/");
+ assertExists(lookup("../work"), "/", "work");
+ assertExists(lookup("../../work"), "/", "work");
+ assertExists(lookup("../foo"), "/", "foo");
+ assertExists(lookup("../work/one/two/../two/three"), "two", "three");
+ assertExists(lookup("one/two/../../four/../one/two/three/../three"), "two", "three");
+ assertExists(lookup("one/two/three/../../two/three/.."), "one", "two");
+ assertExists(lookup("one/two/three/../../two/three/../.."), "work", "one");
+ }
+
+ @Test
+ public void testLookup_relative_withDotDotsInPath_afterSymlink() throws IOException {
+ assertExists(lookup("four/five/.."), "/", "/");
+ assertExists(lookup("four/six/.."), "/", "work");
+ }
+
+ private DirectoryEntry lookup(String path, LinkOption... options) throws IOException {
+ JimfsPath pathObj = pathService.parsePath(path);
+ return fileTree.lookUp(workingDirectory, pathObj, Options.getLinkOptions(options));
+ }
+
+ private void assertExists(DirectoryEntry entry, String parent, String file) {
+ assertThat(entry.exists()).isTrue();
+ assertThat(entry.name()).isEqualTo(Name.simple(file));
+ assertThat(entry.directory()).isEqualTo(files.get(parent));
+ assertThat(entry.file()).isEqualTo(files.get(file));
+ }
+
+ private void assertParentExists(DirectoryEntry entry, String parent) {
+ assertThat(entry.exists()).isFalse();
+ assertThat(entry.directory()).isEqualTo(files.get(parent));
+
+ try {
+ entry.file();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ private File createDirectory(String parent, String name) {
+ Directory dir = (Directory) files.get(parent);
+ Directory newFile = Directory.create(new Random().nextInt());
+ dir.link(Name.simple(name), newFile);
+ files.put(name, newFile);
+ return newFile;
+ }
+
+ private File createFile(String parent, String name) {
+ Directory dir = (Directory) files.get(parent);
+ File newFile = regularFile(0);
+ dir.link(Name.simple(name), newFile);
+ files.put(name, newFile);
+ return newFile;
+ }
+
+ private File createSymbolicLink(String parent, String name, String target) {
+ Directory dir = (Directory) files.get(parent);
+ File newFile = SymbolicLink.create(new Random().nextInt(), pathService.parsePath(target));
+ dir.link(Name.simple(name), newFile);
+ files.put(name, newFile);
+ return newFile;
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java b/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java
new file mode 100644
index 0000000..af09b85
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/HeapDiskTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link HeapDisk}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class HeapDiskTest {
+
+ private RegularFile blocks;
+
+ @Before
+ public void setUp() {
+ // the HeapDisk of this file is unused; it's passed to other HeapDisks to test operations
+ blocks = RegularFile.create(-1, new HeapDisk(2, 2, 2));
+ }
+
+ @Test
+ public void testInitialSettings_basic() {
+ HeapDisk disk = new HeapDisk(8192, 100, 100);
+
+ assertThat(disk.blockSize()).isEqualTo(8192);
+ assertThat(disk.getTotalSpace()).isEqualTo(819200);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(819200);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testInitialSettings_fromConfiguration() {
+ Configuration config =
+ Configuration.unix().toBuilder()
+ .setBlockSize(4)
+ .setMaxSize(99) // not a multiple of 4
+ .setMaxCacheSize(25)
+ .build();
+
+ HeapDisk disk = new HeapDisk(config);
+
+ assertThat(disk.blockSize()).isEqualTo(4);
+ assertThat(disk.getTotalSpace()).isEqualTo(96);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(96);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testAllocate() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 0);
+
+ disk.allocate(blocks, 1);
+
+ assertThat(blocks.blockCount()).isEqualTo(1);
+ assertThat(blocks.getBlock(0).length).isEqualTo(4);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(36);
+
+ disk.allocate(blocks, 5);
+
+ assertThat(blocks.blockCount()).isEqualTo(6);
+ for (int i = 0; i < blocks.blockCount(); i++) {
+ assertThat(blocks.getBlock(i).length).isEqualTo(4);
+ }
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(16);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testFree_noCaching() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 0);
+ disk.allocate(blocks, 6);
+
+ disk.free(blocks, 2);
+ assertThat(blocks.blockCount()).isEqualTo(4);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(24);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(0);
+
+ disk.free(blocks);
+
+ assertThat(blocks.blockCount()).isEqualTo(0);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(40);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testFree_fullCaching() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 10);
+ disk.allocate(blocks, 6);
+
+ disk.free(blocks, 2);
+
+ assertThat(blocks.blockCount()).isEqualTo(4);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(24);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(2);
+
+ disk.free(blocks);
+
+ assertThat(blocks.blockCount()).isEqualTo(0);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(40);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(6);
+ }
+
+ @Test
+ public void testFree_partialCaching() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 4);
+ disk.allocate(blocks, 6);
+
+ disk.free(blocks, 2);
+
+ assertThat(blocks.blockCount()).isEqualTo(4);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(24);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(2);
+
+ disk.free(blocks);
+
+ assertThat(blocks.blockCount()).isEqualTo(0);
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(40);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(4);
+ }
+
+ @Test
+ public void testAllocateFromCache_fullAllocationFromCache() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 10);
+ disk.allocate(blocks, 10);
+
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(0);
+
+ disk.free(blocks);
+
+ assertThat(blocks.blockCount()).isEqualTo(0);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(10);
+
+ List<byte[]> cachedBlocks = new ArrayList<>();
+ for (int i = 0; i < 10; i++) {
+ cachedBlocks.add(disk.blockCache.getBlock(i));
+ }
+
+ disk.allocate(blocks, 6);
+
+ assertThat(blocks.blockCount()).isEqualTo(6);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(4);
+
+ // the 6 arrays in blocks are the last 6 arrays that were cached
+ for (int i = 0; i < 6; i++) {
+ assertThat(blocks.getBlock(i)).isEqualTo(cachedBlocks.get(i + 4));
+ }
+ }
+
+ @Test
+ public void testAllocateFromCache_partialAllocationFromCache() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 4);
+ disk.allocate(blocks, 10);
+
+ assertThat(disk.getUnallocatedSpace()).isEqualTo(0);
+
+ disk.free(blocks);
+
+ assertThat(blocks.blockCount()).isEqualTo(0);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(4);
+
+ List<byte[]> cachedBlocks = new ArrayList<>();
+ for (int i = 0; i < 4; i++) {
+ cachedBlocks.add(disk.blockCache.getBlock(i));
+ }
+
+ disk.allocate(blocks, 6);
+
+ assertThat(blocks.blockCount()).isEqualTo(6);
+ assertThat(disk.blockCache.blockCount()).isEqualTo(0);
+
+ // the last 4 arrays in blocks are the 4 arrays that were cached
+ for (int i = 2; i < 6; i++) {
+ assertThat(blocks.getBlock(i)).isEqualTo(cachedBlocks.get(i - 2));
+ }
+ }
+
+ @Test
+ public void testFullDisk() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 4);
+ disk.allocate(blocks, 10);
+
+ try {
+ disk.allocate(blocks, 1);
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testFullDisk_doesNotAllocatePartiallyWhenTooManyBlocksRequested() throws IOException {
+ HeapDisk disk = new HeapDisk(4, 10, 4);
+ disk.allocate(blocks, 6);
+
+ RegularFile blocks2 = RegularFile.create(-2, disk);
+
+ try {
+ disk.allocate(blocks2, 5);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ assertThat(blocks2.blockCount()).isEqualTo(0);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java
new file mode 100644
index 0000000..7d47588
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsAsynchronousFileChannelTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.buffer;
+import static com.google.common.jimfs.TestUtils.regularFile;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.WRITE;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.Runnables;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.common.util.concurrent.Uninterruptibles;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.CompletionHandler;
+import java.nio.channels.FileLock;
+import java.nio.file.OpenOption;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link JimfsAsynchronousFileChannel}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsAsynchronousFileChannelTest {
+
+ private static JimfsAsynchronousFileChannel channel(
+ RegularFile file, ExecutorService executor, OpenOption... options) throws IOException {
+ JimfsFileChannel channel =
+ new JimfsFileChannel(
+ file,
+ Options.getOptionsForChannel(ImmutableSet.copyOf(options)),
+ new FileSystemState(Runnables.doNothing()));
+ return new JimfsAsynchronousFileChannel(channel, executor);
+ }
+
+ /**
+ * Just tests the main read/write methods... the methods all delegate to the non-async channel
+ * anyway.
+ */
+ @Test
+ public void testAsyncChannel() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+
+ try {
+ assertEquals(15, channel.size());
+
+ assertSame(channel, channel.truncate(5));
+ assertEquals(5, channel.size());
+
+ file.write(5, new byte[5], 0, 5);
+ checkAsyncRead(channel);
+ checkAsyncWrite(channel);
+ checkAsyncLock(channel);
+
+ channel.close();
+ assertFalse(channel.isOpen());
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @Test
+ public void testClosedChannel() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ try {
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+ channel.close();
+
+ assertClosed(channel.read(ByteBuffer.allocate(10), 0));
+ assertClosed(channel.write(ByteBuffer.allocate(10), 15));
+ assertClosed(channel.lock());
+ assertClosed(channel.lock(0, 10, true));
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @Test
+ public void testAsyncClose_write() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newFixedThreadPool(4);
+
+ try {
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+
+ file.writeLock().lock(); // cause another thread trying to write to block
+
+ // future-returning write
+ Future<Integer> future = channel.write(ByteBuffer.allocate(10), 0);
+
+ // completion handler write
+ SettableFuture<Integer> completionHandlerFuture = SettableFuture.create();
+ channel.write(ByteBuffer.allocate(10), 0, null, setFuture(completionHandlerFuture));
+
+ // Despite this 10ms sleep to allow plenty of time, it's possible, though very rare, for a
+ // race to cause the channel to be closed before the asynchronous calls get to the initial
+ // check that the channel is open, causing ClosedChannelException to be thrown rather than
+ // AsynchronousCloseException. This is not a problem in practice, just a quirk of how these
+ // tests work and that we don't have a way of waiting for the operations to get past that
+ // check.
+ Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS);
+
+ channel.close();
+
+ assertAsynchronousClose(future);
+ assertAsynchronousClose(completionHandlerFuture);
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @Test
+ public void testAsyncClose_read() throws Throwable {
+ RegularFile file = regularFile(15);
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+
+ try {
+ JimfsAsynchronousFileChannel channel = channel(file, executor, READ, WRITE);
+
+ file.writeLock().lock(); // cause another thread trying to read to block
+
+ // future-returning read
+ Future<Integer> future = channel.read(ByteBuffer.allocate(10), 0);
+
+ // completion handler read
+ SettableFuture<Integer> completionHandlerFuture = SettableFuture.create();
+ channel.read(ByteBuffer.allocate(10), 0, null, setFuture(completionHandlerFuture));
+
+ // Despite this 10ms sleep to allow plenty of time, it's possible, though very rare, for a
+ // race to cause the channel to be closed before the asynchronous calls get to the initial
+ // check that the channel is open, causing ClosedChannelException to be thrown rather than
+ // AsynchronousCloseException. This is not a problem in practice, just a quirk of how these
+ // tests work and that we don't have a way of waiting for the operations to get past that
+ // check.
+ Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS);
+
+ channel.close();
+
+ assertAsynchronousClose(future);
+ assertAsynchronousClose(completionHandlerFuture);
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ private static void checkAsyncRead(AsynchronousFileChannel channel) throws Throwable {
+ ByteBuffer buf = buffer("1234567890");
+ assertEquals(10, (int) channel.read(buf, 0).get());
+
+ buf.flip();
+
+ SettableFuture<Integer> future = SettableFuture.create();
+ channel.read(buf, 0, null, setFuture(future));
+
+ assertThat(future.get(10, SECONDS)).isEqualTo(10);
+ }
+
+ private static void checkAsyncWrite(AsynchronousFileChannel asyncChannel) throws Throwable {
+ ByteBuffer buf = buffer("1234567890");
+ assertEquals(10, (int) asyncChannel.write(buf, 0).get());
+
+ buf.flip();
+ SettableFuture<Integer> future = SettableFuture.create();
+ asyncChannel.write(buf, 0, null, setFuture(future));
+
+ assertThat(future.get(10, SECONDS)).isEqualTo(10);
+ }
+
+ private static void checkAsyncLock(AsynchronousFileChannel channel) throws Throwable {
+ assertNotNull(channel.lock().get());
+ assertNotNull(channel.lock(0, 10, true).get());
+
+ SettableFuture<FileLock> future = SettableFuture.create();
+ channel.lock(0, 10, true, null, setFuture(future));
+
+ assertNotNull(future.get(10, SECONDS));
+ }
+
+ /**
+ * Returns a {@code CompletionHandler} that sets the appropriate result or exception on the given
+ * {@code future} on completion.
+ */
+ private static <T> CompletionHandler<T, Object> setFuture(final SettableFuture<T> future) {
+ return new CompletionHandler<T, Object>() {
+ @Override
+ public void completed(T result, Object attachment) {
+ future.set(result);
+ }
+
+ @Override
+ public void failed(Throwable exc, Object attachment) {
+ future.setException(exc);
+ }
+ };
+ }
+
+ /** Assert that the future fails, with the failure caused by {@code ClosedChannelException}. */
+ private static void assertClosed(Future<?> future) throws Throwable {
+ try {
+ future.get(10, SECONDS);
+ fail("ChannelClosedException was not thrown");
+ } catch (ExecutionException expected) {
+ assertThat(expected.getCause()).isInstanceOf(ClosedChannelException.class);
+ }
+ }
+
+ /**
+ * Assert that the future fails, with the failure caused by either {@code
+ * AsynchronousCloseException} or (rarely) {@code ClosedChannelException}.
+ */
+ private static void assertAsynchronousClose(Future<?> future) throws Throwable {
+ try {
+ future.get(10, SECONDS);
+ fail("no exception was thrown");
+ } catch (ExecutionException expected) {
+ Throwable t = expected.getCause();
+ if (!(t instanceof AsynchronousCloseException || t instanceof ClosedChannelException)) {
+ fail(
+ "expected AsynchronousCloseException (or in rare cases ClosedChannelException); got "
+ + t);
+ }
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java
new file mode 100644
index 0000000..c525ef1
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileChannelTest.java
@@ -0,0 +1,1049 @@
+/*
+ * 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.assertNotEquals;
+import static com.google.common.jimfs.TestUtils.buffer;
+import static com.google.common.jimfs.TestUtils.bytes;
+import static com.google.common.jimfs.TestUtils.regularFile;
+import static com.google.common.truth.Truth.assertWithMessage;
+import static java.nio.file.StandardOpenOption.APPEND;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.WRITE;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.testing.NullPointerTester;
+import com.google.common.util.concurrent.Runnables;
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.common.util.concurrent.Uninterruptibles;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.FileLockInterruptionException;
+import java.nio.channels.NonReadableChannelException;
+import java.nio.channels.NonWritableChannelException;
+import java.nio.file.OpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Most of the behavior of {@link JimfsFileChannel} is handled by the {@link RegularFile}
+ * implementations, so the thorough tests of that are in {@link RegularFileTest}. This mostly tests
+ * interactions with the file and channel positions.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsFileChannelTest {
+
+ private static FileChannel channel(RegularFile file, OpenOption... options) throws IOException {
+ return new JimfsFileChannel(
+ file,
+ Options.getOptionsForChannel(ImmutableSet.copyOf(options)),
+ new FileSystemState(Runnables.doNothing()));
+ }
+
+ @Test
+ public void testPosition() throws IOException {
+ FileChannel channel = channel(regularFile(10), READ);
+ assertEquals(0, channel.position());
+ assertSame(channel, channel.position(100));
+ assertEquals(100, channel.position());
+ }
+
+ @Test
+ public void testSize() throws IOException {
+ RegularFile file = regularFile(10);
+ FileChannel channel = channel(file, READ);
+
+ assertEquals(10, channel.size());
+
+ file.write(10, new byte[90], 0, 90);
+ assertEquals(100, channel.size());
+ }
+
+ @Test
+ public void testRead() throws IOException {
+ RegularFile file = regularFile(20);
+ FileChannel channel = channel(file, READ);
+ assertEquals(0, channel.position());
+
+ ByteBuffer buf = buffer("1234567890");
+ ByteBuffer buf2 = buffer("123457890");
+ assertEquals(10, channel.read(buf));
+ assertEquals(10, channel.position());
+
+ buf.flip();
+ assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2}));
+ assertEquals(20, channel.position());
+
+ buf.flip();
+ buf2.flip();
+ file.write(20, new byte[10], 0, 10);
+ assertEquals(10, channel.read(new ByteBuffer[] {buf, buf2}, 0, 2));
+ assertEquals(30, channel.position());
+
+ buf.flip();
+ assertEquals(10, channel.read(buf, 5));
+ assertEquals(30, channel.position());
+
+ buf.flip();
+ assertEquals(-1, channel.read(buf));
+ assertEquals(30, channel.position());
+ }
+
+ @Test
+ public void testWrite() throws IOException {
+ RegularFile file = regularFile(0);
+ FileChannel channel = channel(file, WRITE);
+ assertEquals(0, channel.position());
+
+ ByteBuffer buf = buffer("1234567890");
+ ByteBuffer buf2 = buffer("1234567890");
+ assertEquals(10, channel.write(buf));
+ assertEquals(10, channel.position());
+
+ buf.flip();
+ assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}));
+ assertEquals(30, channel.position());
+
+ buf.flip();
+ buf2.flip();
+ assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2));
+ assertEquals(50, channel.position());
+
+ buf.flip();
+ assertEquals(10, channel.write(buf, 5));
+ assertEquals(50, channel.position());
+ }
+
+ @Test
+ public void testAppend() throws IOException {
+ RegularFile file = regularFile(0);
+ FileChannel channel = channel(file, WRITE, APPEND);
+ assertEquals(0, channel.position());
+
+ ByteBuffer buf = buffer("1234567890");
+ ByteBuffer buf2 = buffer("1234567890");
+
+ assertEquals(10, channel.write(buf));
+ assertEquals(10, channel.position());
+
+ buf.flip();
+ channel.position(0);
+ assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}));
+ assertEquals(30, channel.position());
+
+ buf.flip();
+ buf2.flip();
+ channel.position(0);
+ assertEquals(20, channel.write(new ByteBuffer[] {buf, buf2}, 0, 2));
+ assertEquals(50, channel.position());
+
+ buf.flip();
+ channel.position(0);
+ assertEquals(10, channel.write(buf, 5));
+ assertEquals(60, channel.position());
+
+ buf.flip();
+ channel.position(0);
+ assertEquals(10, channel.transferFrom(new ByteBufferChannel(buf), 0, 10));
+ assertEquals(70, channel.position());
+ }
+
+ @Test
+ public void testTransferTo() throws IOException {
+ RegularFile file = regularFile(10);
+ FileChannel channel = channel(file, READ);
+
+ ByteBufferChannel writeChannel = new ByteBufferChannel(buffer("1234567890"));
+ assertEquals(10, channel.transferTo(0, 100, writeChannel));
+ assertEquals(0, channel.position());
+ }
+
+ @Test
+ public void testTransferFrom() throws IOException {
+ RegularFile file = regularFile(0);
+ FileChannel channel = channel(file, WRITE);
+
+ ByteBufferChannel readChannel = new ByteBufferChannel(buffer("1234567890"));
+ assertEquals(10, channel.transferFrom(readChannel, 0, 100));
+ assertEquals(0, channel.position());
+ }
+
+ @Test
+ public void testTruncate() throws IOException {
+ RegularFile file = regularFile(10);
+ FileChannel channel = channel(file, WRITE);
+
+ channel.truncate(10); // no resize, >= size
+ assertEquals(10, file.size());
+ channel.truncate(11); // no resize, > size
+ assertEquals(10, file.size());
+ channel.truncate(5); // resize down to 5
+ assertEquals(5, file.size());
+
+ channel.position(20);
+ channel.truncate(10);
+ assertEquals(10, channel.position());
+ channel.truncate(2);
+ assertEquals(2, channel.position());
+ }
+
+ @Test
+ public void testFileTimeUpdates() throws IOException {
+ RegularFile file = regularFile(10);
+ FileChannel channel =
+ new JimfsFileChannel(
+ file,
+ ImmutableSet.<OpenOption>of(READ, WRITE),
+ new FileSystemState(Runnables.doNothing()));
+
+ // accessed
+ long accessTime = file.getLastAccessTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.read(ByteBuffer.allocate(10));
+ assertNotEquals(accessTime, file.getLastAccessTime());
+
+ accessTime = file.getLastAccessTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.read(ByteBuffer.allocate(10), 0);
+ assertNotEquals(accessTime, file.getLastAccessTime());
+
+ accessTime = file.getLastAccessTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)});
+ assertNotEquals(accessTime, file.getLastAccessTime());
+
+ accessTime = file.getLastAccessTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.read(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1);
+ assertNotEquals(accessTime, file.getLastAccessTime());
+
+ accessTime = file.getLastAccessTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.transferTo(0, 10, new ByteBufferChannel(10));
+ assertNotEquals(accessTime, file.getLastAccessTime());
+
+ // modified
+ long modifiedTime = file.getLastModifiedTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.write(ByteBuffer.allocate(10));
+ assertNotEquals(modifiedTime, file.getLastModifiedTime());
+
+ modifiedTime = file.getLastModifiedTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.write(ByteBuffer.allocate(10), 0);
+ assertNotEquals(modifiedTime, file.getLastModifiedTime());
+
+ modifiedTime = file.getLastModifiedTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)});
+ assertNotEquals(modifiedTime, file.getLastModifiedTime());
+
+ modifiedTime = file.getLastModifiedTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.write(new ByteBuffer[] {ByteBuffer.allocate(10)}, 0, 1);
+ assertNotEquals(modifiedTime, file.getLastModifiedTime());
+
+ modifiedTime = file.getLastModifiedTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.truncate(0);
+ assertNotEquals(modifiedTime, file.getLastModifiedTime());
+
+ modifiedTime = file.getLastModifiedTime();
+ Uninterruptibles.sleepUninterruptibly(2, MILLISECONDS);
+
+ channel.transferFrom(new ByteBufferChannel(10), 0, 10);
+ assertNotEquals(modifiedTime, file.getLastModifiedTime());
+ }
+
+ @Test
+ public void testClose() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ assertTrue(channel.isOpen());
+ channel.close();
+ assertFalse(channel.isOpen());
+
+ try {
+ channel.position();
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.position(0);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.lock();
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.lock(0, 10, true);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.tryLock();
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.tryLock(0, 10, true);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.force(true);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.write(buffer("111"));
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.write(buffer("111"), 10);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.write(new ByteBuffer[] {buffer("111"), buffer("111")});
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.truncate(0);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.read(buffer("111"));
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.read(buffer("111"), 10);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.read(new ByteBuffer[] {buffer("111"), buffer("111")});
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ channel.transferTo(0, 10, new ByteBufferChannel(buffer("111")));
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ executor.shutdown();
+ }
+
+ @Test
+ public void testWritesInReadOnlyMode() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ);
+
+ try {
+ channel.write(buffer("111"));
+ fail();
+ } catch (NonWritableChannelException expected) {
+ }
+
+ try {
+ channel.write(buffer("111"), 10);
+ fail();
+ } catch (NonWritableChannelException expected) {
+ }
+
+ try {
+ channel.write(new ByteBuffer[] {buffer("111"), buffer("111")});
+ fail();
+ } catch (NonWritableChannelException expected) {
+ }
+
+ try {
+ channel.write(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
+ fail();
+ } catch (NonWritableChannelException expected) {
+ }
+
+ try {
+ channel.transferFrom(new ByteBufferChannel(bytes("1111")), 0, 4);
+ fail();
+ } catch (NonWritableChannelException expected) {
+ }
+
+ try {
+ channel.truncate(0);
+ fail();
+ } catch (NonWritableChannelException expected) {
+ }
+
+ try {
+ channel.lock(0, 10, false);
+ fail();
+ } catch (NonWritableChannelException expected) {
+ }
+ }
+
+ @Test
+ public void testReadsInWriteOnlyMode() throws IOException {
+ FileChannel channel = channel(regularFile(0), WRITE);
+
+ try {
+ channel.read(buffer("111"));
+ fail();
+ } catch (NonReadableChannelException expected) {
+ }
+
+ try {
+ channel.read(buffer("111"), 10);
+ fail();
+ } catch (NonReadableChannelException expected) {
+ }
+
+ try {
+ channel.read(new ByteBuffer[] {buffer("111"), buffer("111")});
+ fail();
+ } catch (NonReadableChannelException expected) {
+ }
+
+ try {
+ channel.read(new ByteBuffer[] {buffer("111"), buffer("111")}, 0, 2);
+ fail();
+ } catch (NonReadableChannelException expected) {
+ }
+
+ try {
+ channel.transferTo(0, 10, new ByteBufferChannel(buffer("111")));
+ fail();
+ } catch (NonReadableChannelException expected) {
+ }
+
+ try {
+ channel.lock(0, 10, true);
+ fail();
+ } catch (NonReadableChannelException expected) {
+ }
+ }
+
+ @Test
+ public void testPositionNegative() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+
+ try {
+ channel.position(-1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testTruncateNegative() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+
+ try {
+ channel.truncate(-1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testWriteNegative() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+
+ try {
+ channel.write(buffer("111"), -1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ ByteBuffer[] bufs = {buffer("111"), buffer("111")};
+ try {
+ channel.write(bufs, -1, 10);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+
+ try {
+ channel.write(bufs, 0, -1);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ @Test
+ public void testReadNegative() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+
+ try {
+ channel.read(buffer("111"), -1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ ByteBuffer[] bufs = {buffer("111"), buffer("111")};
+ try {
+ channel.read(bufs, -1, 10);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+
+ try {
+ channel.read(bufs, 0, -1);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ @Test
+ public void testTransferToNegative() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+
+ try {
+ channel.transferTo(-1, 0, new ByteBufferChannel(10));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ channel.transferTo(0, -1, new ByteBufferChannel(10));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testTransferFromNegative() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+
+ try {
+ channel.transferFrom(new ByteBufferChannel(10), -1, 0);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ channel.transferFrom(new ByteBufferChannel(10), 0, -1);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testLockNegative() throws IOException {
+ FileChannel channel = channel(regularFile(0), READ, WRITE);
+
+ try {
+ channel.lock(-1, 10, true);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ channel.lock(0, -1, true);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ channel.tryLock(-1, 10, true);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ channel.tryLock(0, -1, true);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testNullPointerExceptions() throws IOException {
+ FileChannel channel = channel(regularFile(100), READ, WRITE);
+
+ NullPointerTester tester = new NullPointerTester();
+ tester.testAllPublicInstanceMethods(channel);
+ }
+
+ @Test
+ public void testLock() throws IOException {
+ FileChannel channel = channel(regularFile(10), READ, WRITE);
+
+ assertNotNull(channel.lock());
+ assertNotNull(channel.lock(0, 10, false));
+ assertNotNull(channel.lock(0, 10, true));
+
+ assertNotNull(channel.tryLock());
+ assertNotNull(channel.tryLock(0, 10, false));
+ assertNotNull(channel.tryLock(0, 10, true));
+
+ FileLock lock = channel.lock();
+ assertTrue(lock.isValid());
+ lock.release();
+ assertFalse(lock.isValid());
+ }
+
+ @Test
+ public void testAsynchronousClose() throws Exception {
+ RegularFile file = regularFile(10);
+ final FileChannel channel = channel(file, READ, WRITE);
+
+ file.writeLock().lock(); // ensure all operations on the channel will block
+
+ ExecutorService executor = Executors.newCachedThreadPool();
+
+ CountDownLatch latch = new CountDownLatch(BLOCKING_OP_COUNT);
+ List<Future<?>> futures = queueAllBlockingOperations(channel, executor, latch);
+
+ // wait for all the threads to have started running
+ latch.await();
+ // then ensure time for operations to start blocking
+ Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS);
+
+ // close channel on this thread
+ channel.close();
+
+ // the blocking operations are running on different threads, so they all get
+ // AsynchronousCloseException
+ for (Future<?> future : futures) {
+ try {
+ future.get();
+ fail();
+ } catch (ExecutionException expected) {
+ assertWithMessage("blocking thread exception")
+ .that(expected.getCause())
+ .isInstanceOf(AsynchronousCloseException.class);
+ }
+ }
+ }
+
+ @Test
+ public void testCloseByInterrupt() throws Exception {
+ RegularFile file = regularFile(10);
+ final FileChannel channel = channel(file, READ, WRITE);
+
+ file.writeLock().lock(); // ensure all operations on the channel will block
+
+ ExecutorService executor = Executors.newCachedThreadPool();
+
+ final CountDownLatch threadStartLatch = new CountDownLatch(1);
+ final SettableFuture<Throwable> interruptException = SettableFuture.create();
+
+ // This thread, being the first to run, will be blocking on the interruptible lock (the byte
+ // file's write lock) and as such will be interrupted properly... the other threads will be
+ // blocked on the lock that guards the position field and the specification that only one method
+ // on the channel will be in progress at a time. That lock is not interruptible, so we must
+ // interrupt this thread.
+ Thread thread =
+ new Thread(
+ new Runnable() {
+ @Override
+ public void run() {
+ threadStartLatch.countDown();
+ try {
+ channel.write(ByteBuffer.allocate(20));
+ interruptException.set(null);
+ } catch (Throwable e) {
+ interruptException.set(e);
+ }
+ }
+ });
+ thread.start();
+
+ // let the thread start running
+ threadStartLatch.await();
+ // then ensure time for thread to start blocking on the write lock
+ Uninterruptibles.sleepUninterruptibly(10, MILLISECONDS);
+
+ CountDownLatch blockingStartLatch = new CountDownLatch(BLOCKING_OP_COUNT);
+ List<Future<?>> futures = queueAllBlockingOperations(channel, executor, blockingStartLatch);
+
+ // wait for all blocking threads to start
+ blockingStartLatch.await();
+ // then ensure time for the operations to start blocking
+ Uninterruptibles.sleepUninterruptibly(20, MILLISECONDS);
+
+ // interrupting this blocking thread closes the channel and makes all the other threads
+ // throw AsynchronousCloseException... the operation on this thread should throw
+ // ClosedByInterruptException
+ thread.interrupt();
+
+ // get the exception that caused the interrupted operation to terminate
+ assertWithMessage("interrupted thread exception")
+ .that(interruptException.get(200, MILLISECONDS))
+ .isInstanceOf(ClosedByInterruptException.class);
+
+ // check that each other thread got AsynchronousCloseException (since the interrupt, on a
+ // different thread, closed the channel)
+ for (Future<?> future : futures) {
+ try {
+ future.get();
+ fail();
+ } catch (ExecutionException expected) {
+ assertWithMessage("blocking thread exception")
+ .that(expected.getCause())
+ .isInstanceOf(AsynchronousCloseException.class);
+ }
+ }
+ }
+
+ private static final int BLOCKING_OP_COUNT = 10;
+
+ /**
+ * Queues blocking operations on the channel in separate threads using the given executor. The
+ * given latch should have a count of BLOCKING_OP_COUNT to allow the caller wants to wait for all
+ * threads to start executing.
+ */
+ private List<Future<?>> queueAllBlockingOperations(
+ final FileChannel channel, ExecutorService executor, final CountDownLatch startLatch) {
+ List<Future<?>> futures = new ArrayList<>();
+
+ final ByteBuffer buffer = ByteBuffer.allocate(10);
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.write(buffer);
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.write(buffer, 0);
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.write(new ByteBuffer[] {buffer, buffer});
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.write(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2);
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.read(buffer);
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.read(buffer, 0);
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.read(new ByteBuffer[] {buffer, buffer});
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.read(new ByteBuffer[] {buffer, buffer, buffer}, 0, 2);
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.transferTo(0, 10, new ByteBufferChannel(buffer));
+ return null;
+ }
+ }));
+
+ futures.add(
+ executor.submit(
+ new Callable<Object>() {
+ @Override
+ public Object call() throws Exception {
+ startLatch.countDown();
+ channel.transferFrom(new ByteBufferChannel(buffer), 0, 10);
+ return null;
+ }
+ }));
+
+ return futures;
+ }
+
+ /**
+ * Tests that the methods on the default FileChannel that support InterruptibleChannel behavior
+ * also support it on JimfsFileChannel, by just interrupting the thread before calling the method.
+ */
+ @Test
+ public void testInterruptedThreads() throws IOException {
+ final ByteBuffer buf = ByteBuffer.allocate(10);
+ final ByteBuffer[] bufArray = {buf};
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.size();
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.position();
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.position(0);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.write(buf);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.write(bufArray, 0, 1);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.read(buf);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.read(bufArray, 0, 1);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.write(buf, 0);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.read(buf, 0);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.transferTo(0, 1, channel(regularFile(10), READ, WRITE));
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.transferFrom(channel(regularFile(10), READ, WRITE), 0, 1);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.force(true);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.truncate(0);
+ }
+ });
+
+ assertClosedByInterrupt(
+ new FileChannelMethod() {
+ @Override
+ public void call(FileChannel channel) throws IOException {
+ channel.lock(0, 1, true);
+ }
+ });
+
+ // tryLock() does not handle interruption
+ // map() always throws UOE; it doesn't make sense for it to try to handle interruption
+ }
+
+ private interface FileChannelMethod {
+ void call(FileChannel channel) throws IOException;
+ }
+
+ /**
+ * Asserts that when the given operation is run on an interrupted thread, {@code
+ * ClosedByInterruptException} is thrown, the channel is closed and the thread is no longer
+ * interrupted.
+ */
+ private static void assertClosedByInterrupt(FileChannelMethod method) throws IOException {
+ FileChannel channel = channel(regularFile(10), READ, WRITE);
+ Thread.currentThread().interrupt();
+ try {
+ method.call(channel);
+ fail(
+ "expected the method to throw ClosedByInterruptException or "
+ + "FileLockInterruptionException");
+ } catch (ClosedByInterruptException | FileLockInterruptionException expected) {
+ assertFalse("expected the channel to be closed", channel.isOpen());
+ assertTrue("expected the thread to still be interrupted", Thread.interrupted());
+ } finally {
+ Thread.interrupted(); // ensure the thread isn't interrupted when this method returns
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java
new file mode 100644
index 0000000..2e05714
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsFileSystemCloseTest.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2014 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 java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.READ;
+import static java.nio.file.StandardOpenOption.WRITE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.channels.AsynchronousFileChannel;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
+import java.nio.file.ClosedDirectoryStreamException;
+import java.nio.file.ClosedFileSystemException;
+import java.nio.file.ClosedWatchServiceException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.FileSystemNotFoundException;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.WatchService;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for what happens when a file system is closed.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsFileSystemCloseTest {
+
+ private JimfsFileSystem fs = (JimfsFileSystem) Jimfs.newFileSystem(Configuration.unix());
+
+ @Test
+ public void testIsNotOpen() throws IOException {
+ assertTrue(fs.isOpen());
+ fs.close();
+ assertFalse(fs.isOpen());
+ }
+
+ @Test
+ public void testIsNotAvailableFromProvider() throws IOException {
+ URI uri = fs.getUri();
+ assertEquals(fs, FileSystems.getFileSystem(uri));
+
+ fs.close();
+
+ try {
+ FileSystems.getFileSystem(uri);
+ fail();
+ } catch (FileSystemNotFoundException expected) {
+ }
+ }
+
+ @Test
+ public void testOpenStreamsClosed() throws IOException {
+ Path p = fs.getPath("/foo");
+ OutputStream out = Files.newOutputStream(p);
+ InputStream in = Files.newInputStream(p);
+
+ out.write(1);
+ assertEquals(1, in.read());
+
+ fs.close();
+
+ try {
+ out.write(1);
+ fail();
+ } catch (IOException expected) {
+ assertEquals("stream is closed", expected.getMessage());
+ }
+
+ try {
+ in.read();
+ fail();
+ } catch (IOException expected) {
+ assertEquals("stream is closed", expected.getMessage());
+ }
+ }
+
+ @Test
+ public void testOpenChannelsClosed() throws IOException {
+ Path p = fs.getPath("/foo");
+ FileChannel fc = FileChannel.open(p, READ, WRITE, CREATE);
+ SeekableByteChannel sbc = Files.newByteChannel(p, READ);
+ AsynchronousFileChannel afc = AsynchronousFileChannel.open(p, READ, WRITE);
+
+ assertTrue(fc.isOpen());
+ assertTrue(sbc.isOpen());
+ assertTrue(afc.isOpen());
+
+ fs.close();
+
+ assertFalse(fc.isOpen());
+ assertFalse(sbc.isOpen());
+ assertFalse(afc.isOpen());
+
+ try {
+ fc.size();
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ sbc.size();
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+
+ try {
+ afc.size();
+ fail();
+ } catch (ClosedChannelException expected) {
+ }
+ }
+
+ @Test
+ public void testOpenDirectoryStreamsClosed() throws IOException {
+ Path p = fs.getPath("/foo");
+ Files.createDirectory(p);
+
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
+
+ fs.close();
+
+ try {
+ stream.iterator();
+ fail();
+ } catch (ClosedDirectoryStreamException expected) {
+ }
+ }
+ }
+
+ @Test
+ public void testOpenWatchServicesClosed() throws IOException {
+ WatchService ws1 = fs.newWatchService();
+ WatchService ws2 = fs.newWatchService();
+
+ assertNull(ws1.poll());
+ assertNull(ws2.poll());
+
+ fs.close();
+
+ try {
+ ws1.poll();
+ fail();
+ } catch (ClosedWatchServiceException expected) {
+ }
+
+ try {
+ ws2.poll();
+ fail();
+ } catch (ClosedWatchServiceException expected) {
+ }
+ }
+
+ @Test
+ public void testPathMethodsThrow() throws IOException {
+ Path p = fs.getPath("/foo");
+ Files.createDirectory(p);
+
+ WatchService ws = fs.newWatchService();
+
+ fs.close();
+
+ try {
+ p.register(ws, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
+ fail();
+ } catch (ClosedWatchServiceException expected) {
+ }
+
+ try {
+ p = p.toRealPath();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ // While technically (according to the FileSystem.close() spec) all methods on Path should
+ // probably throw, we only throw for methods that access the file system itself in some way...
+ // path manipulation methods seem totally harmless to keep working, and I don't see any need to
+ // add the overhead of checking that the file system is open for each of those method calls.
+ }
+
+ @Test
+ public void testOpenFileAttributeViewsThrow() throws IOException {
+ Path p = fs.getPath("/foo");
+ Files.createFile(p);
+
+ BasicFileAttributeView view = Files.getFileAttributeView(p, BasicFileAttributeView.class);
+
+ fs.close();
+
+ try {
+ view.readAttributes();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ view.setTimes(null, null, null);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+ }
+
+ @Test
+ public void testFileSystemMethodsThrow() throws IOException {
+ fs.close();
+
+ try {
+ fs.getPath("/foo");
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ fs.getRootDirectories();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ fs.getFileStores();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ fs.getPathMatcher("glob:*.java");
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ fs.getUserPrincipalLookupService();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ fs.newWatchService();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ fs.supportedFileAttributeViews();
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+ }
+
+ @Test
+ public void testFilesMethodsThrow() throws IOException {
+ Path file = fs.getPath("/file");
+ Path dir = fs.getPath("/dir");
+ Path nothing = fs.getPath("/nothing");
+
+ Files.createDirectory(dir);
+ Files.createFile(file);
+
+ fs.close();
+
+ // not exhaustive, but should cover every major type of functionality accessible through Files
+ // TODO(cgdecker): reflectively invoke all methods with default arguments?
+
+ try {
+ Files.delete(file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.createDirectory(nothing);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.createFile(nothing);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.write(nothing, ImmutableList.of("hello world"), UTF_8);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.newInputStream(file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.newOutputStream(file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.newByteChannel(file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.newDirectoryStream(dir);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.copy(file, nothing);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.move(file, nothing);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.copy(dir, nothing);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.move(dir, nothing);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.createSymbolicLink(nothing, file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.createLink(nothing, file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.exists(file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.getAttribute(file, "size");
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.setAttribute(file, "lastModifiedTime", FileTime.fromMillis(0));
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.getFileAttributeView(file, BasicFileAttributeView.class);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.readAttributes(file, "basic:size,lastModifiedTime");
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.readAttributes(file, BasicFileAttributes.class);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.isDirectory(dir);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.readAllBytes(file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+
+ try {
+ Files.isReadable(file);
+ fail();
+ } catch (ClosedFileSystemException expected) {
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java
new file mode 100644
index 0000000..94cc5f4
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsInputStreamTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.regularFile;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.Runnables;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link JimfsInputStream}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+@SuppressWarnings("ResultOfMethodCallIgnored")
+public class JimfsInputStreamTest {
+
+ @Test
+ public void testRead_singleByte() throws IOException {
+ JimfsInputStream in = newInputStream(2);
+ assertThat(in.read()).isEqualTo(2);
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testRead_wholeArray() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ byte[] bytes = new byte[8];
+ assertThat(in.read(bytes)).isEqualTo(8);
+ assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8), bytes);
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testRead_wholeArray_arrayLarger() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ byte[] bytes = new byte[12];
+ assertThat(in.read(bytes)).isEqualTo(8);
+ assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes);
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testRead_wholeArray_arraySmaller() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ byte[] bytes = new byte[6];
+ assertThat(in.read(bytes)).isEqualTo(6);
+ assertArrayEquals(bytes(1, 2, 3, 4, 5, 6), bytes);
+ bytes = new byte[6];
+ assertThat(in.read(bytes)).isEqualTo(2);
+ assertArrayEquals(bytes(7, 8, 0, 0, 0, 0), bytes);
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testRead_partialArray() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ byte[] bytes = new byte[12];
+ assertThat(in.read(bytes, 0, 8)).isEqualTo(8);
+ assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes);
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testRead_partialArray_sliceLarger() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ byte[] bytes = new byte[12];
+ assertThat(in.read(bytes, 0, 10)).isEqualTo(8);
+ assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes);
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testRead_partialArray_sliceSmaller() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ byte[] bytes = new byte[12];
+ assertThat(in.read(bytes, 0, 6)).isEqualTo(6);
+ assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 0, 0, 0, 0, 0, 0), bytes);
+ assertThat(in.read(bytes, 6, 6)).isEqualTo(2);
+ assertArrayEquals(bytes(1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 0), bytes);
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testRead_partialArray_invalidInput() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5);
+
+ try {
+ in.read(new byte[3], -1, 1);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+
+ try {
+ in.read(new byte[3], 0, 4);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+
+ try {
+ in.read(new byte[3], 1, 3);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ @Test
+ public void testAvailable() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ assertThat(in.available()).isEqualTo(8);
+ assertThat(in.read()).isEqualTo(1);
+ assertThat(in.available()).isEqualTo(7);
+ assertThat(in.read(new byte[3])).isEqualTo(3);
+ assertThat(in.available()).isEqualTo(4);
+ assertThat(in.read(new byte[10], 1, 2)).isEqualTo(2);
+ assertThat(in.available()).isEqualTo(2);
+ assertThat(in.read(new byte[10])).isEqualTo(2);
+ assertThat(in.available()).isEqualTo(0);
+ }
+
+ @Test
+ public void testSkip() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5, 6, 7, 8);
+ assertThat(in.skip(0)).isEqualTo(0);
+ assertThat(in.skip(-10)).isEqualTo(0);
+ assertThat(in.skip(2)).isEqualTo(2);
+ assertThat(in.read()).isEqualTo(3);
+ assertThat(in.skip(3)).isEqualTo(3);
+ assertThat(in.read()).isEqualTo(7);
+ assertThat(in.skip(10)).isEqualTo(1);
+ assertEmpty(in);
+ assertThat(in.skip(10)).isEqualTo(0);
+ assertEmpty(in);
+ }
+
+ @SuppressWarnings("GuardedByChecker")
+ @Test
+ public void testFullyReadInputStream_doesNotChangeStateWhenStoreChanges() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3, 4, 5);
+ assertThat(in.read(new byte[5])).isEqualTo(5);
+ assertEmpty(in);
+
+ in.file.write(5, new byte[10], 0, 10); // append more bytes to file
+ assertEmpty(in);
+ }
+
+ @Test
+ public void testMark_unsupported() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3);
+ assertThat(in.markSupported()).isFalse();
+
+ // mark does nothing
+ in.mark(1);
+
+ try {
+ // reset throws IOException when unsupported
+ in.reset();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testClosedInputStream_throwsException() throws IOException {
+ JimfsInputStream in = newInputStream(1, 2, 3);
+ in.close();
+
+ try {
+ in.read();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ in.read(new byte[3]);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ in.read(new byte[10], 0, 2);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ in.skip(10);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ in.available();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ in.close(); // does nothing
+ }
+
+ private static JimfsInputStream newInputStream(int... bytes) throws IOException {
+ byte[] b = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ b[i] = (byte) bytes[i];
+ }
+
+ RegularFile file = regularFile(0);
+ file.write(0, b, 0, b.length);
+ return new JimfsInputStream(file, new FileSystemState(Runnables.doNothing()));
+ }
+
+ private static void assertEmpty(JimfsInputStream in) throws IOException {
+ assertThat(in.read()).isEqualTo(-1);
+ assertThat(in.read(new byte[3])).isEqualTo(-1);
+ assertThat(in.read(new byte[10], 1, 5)).isEqualTo(-1);
+ assertThat(in.available()).isEqualTo(0);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java
new file mode 100644
index 0000000..3c230a7
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsOutputStreamTest.java
@@ -0,0 +1,205 @@
+/*
+ * 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.regularFile;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.util.concurrent.Runnables;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link JimfsOutputStream}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsOutputStreamTest {
+
+ @Test
+ public void testWrite_singleByte() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ out.write(1);
+ out.write(2);
+ out.write(3);
+ assertStoreContains(out, 1, 2, 3);
+ }
+
+ @Test
+ public void testWrite_wholeArray() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ out.write(new byte[] {1, 2, 3, 4});
+ assertStoreContains(out, 1, 2, 3, 4);
+ }
+
+ @Test
+ public void testWrite_partialArray() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ out.write(new byte[] {1, 2, 3, 4, 5, 6}, 1, 3);
+ assertStoreContains(out, 2, 3, 4);
+ }
+
+ @Test
+ public void testWrite_partialArray_invalidInput() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+
+ try {
+ out.write(new byte[3], -1, 1);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+
+ try {
+ out.write(new byte[3], 0, 4);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+
+ try {
+ out.write(new byte[3], 1, 3);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {
+ }
+ }
+
+ @Test
+ public void testWrite_singleByte_appendMode() throws IOException {
+ JimfsOutputStream out = newOutputStream(true);
+ addBytesToStore(out, 9, 8, 7);
+ out.write(1);
+ out.write(2);
+ out.write(3);
+ assertStoreContains(out, 9, 8, 7, 1, 2, 3);
+ }
+
+ @Test
+ public void testWrite_wholeArray_appendMode() throws IOException {
+ JimfsOutputStream out = newOutputStream(true);
+ addBytesToStore(out, 9, 8, 7);
+ out.write(new byte[] {1, 2, 3, 4});
+ assertStoreContains(out, 9, 8, 7, 1, 2, 3, 4);
+ }
+
+ @Test
+ public void testWrite_partialArray_appendMode() throws IOException {
+ JimfsOutputStream out = newOutputStream(true);
+ addBytesToStore(out, 9, 8, 7);
+ out.write(new byte[] {1, 2, 3, 4, 5, 6}, 1, 3);
+ assertStoreContains(out, 9, 8, 7, 2, 3, 4);
+ }
+
+ @Test
+ public void testWrite_singleByte_overwriting() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ addBytesToStore(out, 9, 8, 7, 6, 5, 4, 3);
+ out.write(1);
+ out.write(2);
+ out.write(3);
+ assertStoreContains(out, 1, 2, 3, 6, 5, 4, 3);
+ }
+
+ @Test
+ public void testWrite_wholeArray_overwriting() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ addBytesToStore(out, 9, 8, 7, 6, 5, 4, 3);
+ out.write(new byte[] {1, 2, 3, 4});
+ assertStoreContains(out, 1, 2, 3, 4, 5, 4, 3);
+ }
+
+ @Test
+ public void testWrite_partialArray_overwriting() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ addBytesToStore(out, 9, 8, 7, 6, 5, 4, 3);
+ out.write(new byte[] {1, 2, 3, 4, 5, 6}, 1, 3);
+ assertStoreContains(out, 2, 3, 4, 6, 5, 4, 3);
+ }
+
+ @Test
+ public void testClosedOutputStream_throwsException() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ out.close();
+
+ try {
+ out.write(1);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ out.write(new byte[3]);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ out.write(new byte[10], 1, 3);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ out.close(); // does nothing
+ }
+
+ @Test
+ public void testClosedOutputStream_doesNotThrowOnFlush() throws IOException {
+ JimfsOutputStream out = newOutputStream(false);
+ out.close();
+ out.flush(); // does nothing
+
+ try (JimfsOutputStream out2 = newOutputStream(false);
+ BufferedOutputStream bout = new BufferedOutputStream(out2);
+ OutputStreamWriter writer = new OutputStreamWriter(bout, UTF_8)) {
+ /*
+ * This specific scenario is why flush() shouldn't throw when the stream is already closed.
+ * Nesting try-with-resources like this will cause close() to be called on the
+ * BufferedOutputStream multiple times. Each time, BufferedOutputStream will first call
+ * out2.flush(), then call out2.close(). If out2.flush() throws when the stream is already
+ * closed, the second flush() will throw an exception. Prior to JDK8, this exception would be
+ * swallowed and ignored completely; in JDK8, the exception is thrown from close().
+ */
+ }
+ }
+
+ private static JimfsOutputStream newOutputStream(boolean append) {
+ RegularFile file = regularFile(0);
+ return new JimfsOutputStream(file, append, new FileSystemState(Runnables.doNothing()));
+ }
+
+ @SuppressWarnings("GuardedByChecker")
+ private static void addBytesToStore(JimfsOutputStream out, int... bytes) throws IOException {
+ RegularFile file = out.file;
+ long pos = file.sizeWithoutLocking();
+ for (int b : bytes) {
+ file.write(pos++, (byte) b);
+ }
+ }
+
+ @SuppressWarnings("GuardedByChecker")
+ private static void assertStoreContains(JimfsOutputStream out, int... bytes) {
+ byte[] actualBytes = new byte[bytes.length];
+ out.file.read(0, actualBytes, 0, actualBytes.length);
+ assertArrayEquals(bytes(bytes), actualBytes);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsPathTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsPathTest.java
new file mode 100644
index 0000000..da7d43c
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsPathTest.java
@@ -0,0 +1,385 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.testing.EqualsTester;
+import com.google.common.testing.NullPointerTester;
+import java.io.IOException;
+import java.nio.file.InvalidPathException;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link JimfsPath}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsPathTest {
+
+ private final PathService pathService = PathServiceTest.fakeUnixPathService();
+
+ @Test
+ public void testPathParsing() {
+ assertPathEquals("/", "/");
+ assertPathEquals("/foo", "/foo");
+ assertPathEquals("/foo", "/", "foo");
+ assertPathEquals("/foo/bar", "/foo/bar");
+ assertPathEquals("/foo/bar", "/", "foo", "bar");
+ assertPathEquals("/foo/bar", "/foo", "bar");
+ assertPathEquals("/foo/bar", "/", "foo/bar");
+ assertPathEquals("foo/bar/baz", "foo/bar/baz");
+ assertPathEquals("foo/bar/baz", "foo", "bar", "baz");
+ assertPathEquals("foo/bar/baz", "foo/bar", "baz");
+ assertPathEquals("foo/bar/baz", "foo", "bar/baz");
+ }
+
+ @Test
+ public void testPathParsing_withExtraSeparators() {
+ assertPathEquals("/foo/bar", "///foo/bar");
+ assertPathEquals("/foo/bar", "/foo///bar//");
+ assertPathEquals("/foo/bar/baz", "/foo", "/bar", "baz/");
+ // assertPathEquals("/foo/bar/baz", "/foo\\/bar//\\\\/baz\\/");
+ }
+
+ @Test
+ public void testPathParsing_windowsStylePaths() throws IOException {
+ PathService windowsPathService = PathServiceTest.fakeWindowsPathService();
+ assertEquals("C:\\", pathService.parsePath("C:\\").toString());
+ assertEquals("C:\\foo", windowsPathService.parsePath("C:\\foo").toString());
+ assertEquals("C:\\foo", windowsPathService.parsePath("C:\\", "foo").toString());
+ assertEquals("C:\\foo", windowsPathService.parsePath("C:", "\\foo").toString());
+ assertEquals("C:\\foo", windowsPathService.parsePath("C:", "foo").toString());
+ assertEquals("C:\\foo\\bar", windowsPathService.parsePath("C:", "foo/bar").toString());
+ }
+
+ @Test
+ public void testParsing_windowsStylePaths_invalidPaths() {
+ PathService windowsPathService = PathServiceTest.fakeWindowsPathService();
+
+ try {
+ // The actual windows implementation seems to allow "C:" but treat it as a *name*, not a root
+ // despite the fact that a : is illegal except in a root... a : at any position other than
+ // index 1 in the string will produce an exception.
+ // Here, I choose to be more strict
+ windowsPathService.parsePath("C:");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+
+ try {
+ // "1:\" isn't a root because 1 isn't a letter
+ windowsPathService.parsePath("1:\\foo");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+
+ try {
+ // < and > are reserved characters
+ windowsPathService.parsePath("foo<bar>");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+ }
+
+ @Test
+ public void testPathParsing_withAlternateSeparator() {
+ // windows recognizes / as an alternate separator
+ PathService windowsPathService = PathServiceTest.fakeWindowsPathService();
+ assertEquals(
+ windowsPathService.parsePath("foo\\bar\\baz"), windowsPathService.parsePath("foo/bar/baz"));
+ assertEquals(
+ windowsPathService.parsePath("C:\\foo\\bar"), windowsPathService.parsePath("C:\\foo/bar"));
+ assertEquals(
+ windowsPathService.parsePath("c:\\foo\\bar\\baz"),
+ windowsPathService.parsePath("c:", "foo/", "bar/baz"));
+ }
+
+ @Test
+ public void testRootPath() {
+ new PathTester(pathService, "/").root("/").test("/");
+ }
+
+ @Test
+ public void testRelativePath_singleName() {
+ new PathTester(pathService, "test").names("test").test("test");
+
+ Path path = pathService.parsePath("test");
+ assertEquals(path, path.getFileName());
+ }
+
+ @Test
+ public void testRelativePath_twoNames() {
+ PathTester tester = new PathTester(pathService, "foo/bar").names("foo", "bar");
+
+ tester.test("foo/bar");
+ }
+
+ @Test
+ public void testRelativePath_fourNames() {
+ new PathTester(pathService, "foo/bar/baz/test")
+ .names("foo", "bar", "baz", "test")
+ .test("foo/bar/baz/test");
+ }
+
+ @Test
+ public void testAbsolutePath_singleName() {
+ new PathTester(pathService, "/foo").root("/").names("foo").test("/foo");
+ }
+
+ @Test
+ public void testAbsolutePath_twoNames() {
+ new PathTester(pathService, "/foo/bar").root("/").names("foo", "bar").test("/foo/bar");
+ }
+
+ @Test
+ public void testAbsoluteMultiNamePath_fourNames() {
+ new PathTester(pathService, "/foo/bar/baz/test")
+ .root("/")
+ .names("foo", "bar", "baz", "test")
+ .test("/foo/bar/baz/test");
+ }
+
+ @Test
+ public void testResolve_fromRoot() {
+ Path root = pathService.parsePath("/");
+
+ assertResolvedPathEquals("/foo", root, "foo");
+ assertResolvedPathEquals("/foo/bar", root, "foo/bar");
+ assertResolvedPathEquals("/foo/bar", root, "foo", "bar");
+ assertResolvedPathEquals("/foo/bar/baz/test", root, "foo/bar/baz/test");
+ assertResolvedPathEquals("/foo/bar/baz/test", root, "foo", "bar/baz", "test");
+ }
+
+ @Test
+ public void testResolve_fromAbsolute() {
+ Path path = pathService.parsePath("/foo");
+
+ assertResolvedPathEquals("/foo/bar", path, "bar");
+ assertResolvedPathEquals("/foo/bar/baz/test", path, "bar/baz/test");
+ assertResolvedPathEquals("/foo/bar/baz/test", path, "bar/baz", "test");
+ assertResolvedPathEquals("/foo/bar/baz/test", path, "bar", "baz", "test");
+ }
+
+ @Test
+ public void testResolve_fromRelative() {
+ Path path = pathService.parsePath("foo");
+
+ assertResolvedPathEquals("foo/bar", path, "bar");
+ assertResolvedPathEquals("foo/bar/baz/test", path, "bar/baz/test");
+ assertResolvedPathEquals("foo/bar/baz/test", path, "bar", "baz", "test");
+ assertResolvedPathEquals("foo/bar/baz/test", path, "bar/baz", "test");
+ }
+
+ @Test
+ public void testResolve_withThisAndParentDirNames() {
+ Path path = pathService.parsePath("/foo");
+
+ assertResolvedPathEquals("/foo/bar/../baz", path, "bar/../baz");
+ assertResolvedPathEquals("/foo/bar/../baz", path, "bar", "..", "baz");
+ assertResolvedPathEquals("/foo/./bar/baz", path, "./bar/baz");
+ assertResolvedPathEquals("/foo/./bar/baz", path, ".", "bar/baz");
+ }
+
+ @Test
+ public void testResolve_givenAbsolutePath() {
+ assertResolvedPathEquals("/test", pathService.parsePath("/foo"), "/test");
+ assertResolvedPathEquals("/test", pathService.parsePath("foo"), "/test");
+ }
+
+ @Test
+ public void testResolve_givenEmptyPath() {
+ assertResolvedPathEquals("/foo", pathService.parsePath("/foo"), "");
+ assertResolvedPathEquals("foo", pathService.parsePath("foo"), "");
+ }
+
+ @Test
+ public void testResolve_againstEmptyPath() {
+ assertResolvedPathEquals("foo/bar", pathService.emptyPath(), "foo/bar");
+ }
+
+ @Test
+ public void testResolveSibling_givenEmptyPath() {
+ Path path = pathService.parsePath("foo/bar");
+ Path resolved = path.resolveSibling("");
+ assertPathEquals("foo", resolved);
+
+ path = pathService.parsePath("foo");
+ resolved = path.resolveSibling("");
+ assertPathEquals("", resolved);
+ }
+
+ @Test
+ public void testResolveSibling_againstEmptyPath() {
+ Path path = pathService.parsePath("");
+ Path resolved = path.resolveSibling("foo");
+ assertPathEquals("foo", resolved);
+
+ path = pathService.parsePath("");
+ resolved = path.resolveSibling("");
+ assertPathEquals("", resolved);
+ }
+
+ @Test
+ public void testRelativize_bothAbsolute() {
+ // TODO(cgdecker): When the paths have different roots, how should this work?
+ // Should it work at all?
+ assertRelativizedPathEquals("b/c", pathService.parsePath("/a"), "/a/b/c");
+ assertRelativizedPathEquals("c/d", pathService.parsePath("/a/b"), "/a/b/c/d");
+ }
+
+ @Test
+ public void testRelativize_bothRelative() {
+ assertRelativizedPathEquals("b/c", pathService.parsePath("a"), "a/b/c");
+ assertRelativizedPathEquals("d", pathService.parsePath("a/b/c"), "a/b/c/d");
+ }
+
+ @Test
+ public void testRelativize_againstEmptyPath() {
+ assertRelativizedPathEquals("foo/bar", pathService.emptyPath(), "foo/bar");
+ }
+
+ @Test
+ public void testRelativize_oneAbsoluteOneRelative() {
+ try {
+ pathService.parsePath("/foo/bar").relativize(pathService.parsePath("foo"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ pathService.parsePath("foo").relativize(pathService.parsePath("/foo/bar"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testNormalize_withParentDirName() {
+ assertNormalizedPathEquals("/foo/baz", "/foo/bar/../baz");
+ assertNormalizedPathEquals("/foo/baz", "/foo", "bar", "..", "baz");
+ }
+
+ @Test
+ public void testNormalize_withThisDirName() {
+ assertNormalizedPathEquals("/foo/bar/baz", "/foo/bar/./baz");
+ assertNormalizedPathEquals("/foo/bar/baz", "/foo", "bar", ".", "baz");
+ }
+
+ @Test
+ public void testNormalize_withThisAndParentDirNames() {
+ assertNormalizedPathEquals("foo/test", "foo/./bar/../././baz/../test");
+ }
+
+ @Test
+ public void testNormalize_withLeadingParentDirNames() {
+ assertNormalizedPathEquals("../../foo/baz", "../../foo/bar/../baz");
+ }
+
+ @Test
+ public void testNormalize_withLeadingThisAndParentDirNames() {
+ assertNormalizedPathEquals("../../foo/baz", "./.././.././foo/bar/../baz");
+ }
+
+ @Test
+ public void testNormalize_withExtraParentDirNamesAtRoot() {
+ assertNormalizedPathEquals("/", "/..");
+ assertNormalizedPathEquals("/", "/../../..");
+ assertNormalizedPathEquals("/", "/foo/../../..");
+ assertNormalizedPathEquals("/", "/../foo/../../bar/baz/../../../..");
+ }
+
+ @Test
+ public void testPathWithExtraSlashes() {
+ assertPathEquals("/foo/bar/baz", pathService.parsePath("/foo/bar/baz/"));
+ assertPathEquals("/foo/bar/baz", pathService.parsePath("/foo//bar///baz"));
+ assertPathEquals("/foo/bar/baz", pathService.parsePath("///foo/bar/baz"));
+ }
+
+ @Test
+ public void testEqualityBasedOnStringNotName() {
+ Name a1 = Name.create("a", "a");
+ Name a2 = Name.create("A", "a");
+ Name a3 = Name.create("a", "A");
+
+ Path path1 = pathService.createFileName(a1);
+ Path path2 = pathService.createFileName(a2);
+ Path path3 = pathService.createFileName(a3);
+
+ new EqualsTester().addEqualityGroup(path1, path3).addEqualityGroup(path2).testEquals();
+ }
+
+ @Test
+ public void testNullPointerExceptions() throws NoSuchMethodException {
+ NullPointerTester tester =
+ new NullPointerTester().ignore(JimfsPath.class.getMethod("toRealPath", LinkOption[].class));
+ // ignore toRealPath because the pathService creates fake paths that do not have a
+ // JimfsFileSystem instance, causing it to fail since it needs to access the file system
+
+ tester.testAllPublicInstanceMethods(pathService.parsePath("/"));
+ tester.testAllPublicInstanceMethods(pathService.parsePath(""));
+ tester.testAllPublicInstanceMethods(pathService.parsePath("/foo"));
+ tester.testAllPublicInstanceMethods(pathService.parsePath("/foo/bar/baz"));
+ tester.testAllPublicInstanceMethods(pathService.parsePath("foo"));
+ tester.testAllPublicInstanceMethods(pathService.parsePath("foo/bar"));
+ tester.testAllPublicInstanceMethods(pathService.parsePath("foo/bar/baz"));
+ tester.testAllPublicInstanceMethods(pathService.parsePath("."));
+ tester.testAllPublicInstanceMethods(pathService.parsePath(".."));
+ }
+
+ private void assertResolvedPathEquals(
+ String expected, Path path, String firstResolvePath, String... moreResolvePaths) {
+ Path resolved = path.resolve(firstResolvePath);
+ for (String additionalPath : moreResolvePaths) {
+ resolved = resolved.resolve(additionalPath);
+ }
+ assertPathEquals(expected, resolved);
+
+ Path relative = pathService.parsePath(firstResolvePath, moreResolvePaths);
+ resolved = path.resolve(relative);
+ assertPathEquals(expected, resolved);
+
+ // assert the invariant that p.relativize(p.resolve(q)).equals(q) when q does not have a root
+ // p = path, q = relative, p.resolve(q) = resolved
+ if (relative.getRoot() == null) {
+ assertEquals(relative, path.relativize(resolved));
+ }
+ }
+
+ private void assertRelativizedPathEquals(String expected, Path path, String relativizePath) {
+ Path relativized = path.relativize(pathService.parsePath(relativizePath));
+ assertPathEquals(expected, relativized);
+ }
+
+ private void assertNormalizedPathEquals(String expected, String first, String... more) {
+ assertPathEquals(expected, pathService.parsePath(first, more).normalize());
+ }
+
+ private void assertPathEquals(String expected, String first, String... more) {
+ assertPathEquals(expected, pathService.parsePath(first, more));
+ }
+
+ private void assertPathEquals(String expected, Path path) {
+ assertEquals(pathService.parsePath(expected), path);
+ }
+}
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);
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/JimfsWindowsLikeFileSystemTest.java b/jimfs/src/test/java/com/google/common/jimfs/JimfsWindowsLikeFileSystemTest.java
new file mode 100644
index 0000000..a3b7ad2
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/JimfsWindowsLikeFileSystemTest.java
@@ -0,0 +1,491 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystemException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.regex.PatternSyntaxException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests a Windows-like file system through the public methods in {@link Files}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class JimfsWindowsLikeFileSystemTest extends AbstractJimfsIntegrationTest {
+
+ @Override
+ protected FileSystem createFileSystem() {
+ return Jimfs.newFileSystem(
+ "win",
+ Configuration.windows().toBuilder()
+ .setRoots("C:\\", "E:\\")
+ .setAttributeViews("basic", "owner", "dos", "acl", "user")
+ .build());
+ }
+
+ @Test
+ public void testFileSystem() {
+ assertThat(fs.getSeparator()).isEqualTo("\\");
+ assertThat(fs.getRootDirectories())
+ .containsExactlyElementsIn(ImmutableSet.of(path("C:\\"), path("E:\\")))
+ .inOrder();
+ assertThat(fs.isOpen()).isTrue();
+ assertThat(fs.isReadOnly()).isFalse();
+ assertThat(fs.supportedFileAttributeViews())
+ .containsExactly("basic", "owner", "dos", "acl", "user");
+ assertThat(fs.provider()).isInstanceOf(JimfsFileSystemProvider.class);
+ }
+
+ @Test
+ public void testPaths() {
+ assertThatPath("C:\\").isAbsolute().and().hasRootComponent("C:\\").and().hasNoNameComponents();
+ assertThatPath("foo").isRelative().and().hasNameComponents("foo");
+ assertThatPath("foo\\bar").isRelative().and().hasNameComponents("foo", "bar");
+ assertThatPath("C:\\foo\\bar\\baz")
+ .isAbsolute()
+ .and()
+ .hasRootComponent("C:\\")
+ .and()
+ .hasNameComponents("foo", "bar", "baz");
+ }
+
+ @Test
+ public void testPaths_equalityIsCaseInsensitive() {
+ assertThatPath("C:\\").isEqualTo(path("c:\\"));
+ assertThatPath("foo").isEqualTo(path("FOO"));
+ }
+
+ @Test
+ public void testPaths_areSortedCaseInsensitive() {
+ 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(p1, p2, p3, p4));
+
+ // would be p2, p4, p1, p3 if sorting were case sensitive
+ }
+
+ @Test
+ public void testPaths_withSlash() {
+ assertThatPath("foo/bar")
+ .isRelative()
+ .and()
+ .hasNameComponents("foo", "bar")
+ .and()
+ .isEqualTo(path("foo\\bar"));
+ assertThatPath("C:/foo/bar/baz")
+ .isAbsolute()
+ .and()
+ .hasRootComponent("C:\\")
+ .and()
+ .hasNameComponents("foo", "bar", "baz")
+ .and()
+ .isEqualTo(path("C:\\foo\\bar\\baz"));
+ assertThatPath("C:/foo\\bar/baz")
+ .isAbsolute()
+ .and()
+ .hasRootComponent("C:\\")
+ .and()
+ .hasNameComponents("foo", "bar", "baz")
+ .and()
+ .isEqualTo(path("C:\\foo\\bar\\baz"));
+ }
+
+ @Test
+ public void testPaths_resolve() {
+ assertThatPath(path("C:\\").resolve("foo\\bar"))
+ .isAbsolute()
+ .and()
+ .hasRootComponent("C:\\")
+ .and()
+ .hasNameComponents("foo", "bar");
+ assertThatPath(path("foo\\bar").resolveSibling("baz"))
+ .isRelative()
+ .and()
+ .hasNameComponents("foo", "baz");
+ assertThatPath(path("foo\\bar").resolve("C:\\one\\two"))
+ .isAbsolute()
+ .and()
+ .hasRootComponent("C:\\")
+ .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("C:\\foo\\bar").relativize(path("C:\\foo\\bar\\baz")))
+ .isRelative()
+ .and()
+ .hasNameComponents("baz");
+ assertThatPath(path("C:\\foo\\bar\\baz").relativize(path("C:\\foo\\bar")))
+ .isRelative()
+ .and()
+ .hasNameComponents("..");
+ assertThatPath(path("C:\\foo\\bar\\baz").relativize(path("C:\\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("C:\\foo\\bar").relativize(path("bar"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+
+ try {
+ Path unused = path("bar").relativize(path("C:\\foo\\bar"));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @Test
+ public void testPaths_startsWith_endsWith() {
+ assertThat(path("C:\\foo\\bar").startsWith("C:\\")).isTrue();
+ assertThat(path("C:\\foo\\bar").startsWith("C:\\foo")).isTrue();
+ assertThat(path("C:\\foo\\bar").startsWith("C:\\foo\\bar")).isTrue();
+ assertThat(path("C:\\foo\\bar").endsWith("bar")).isTrue();
+ assertThat(path("C:\\foo\\bar").endsWith("foo\\bar")).isTrue();
+ assertThat(path("C:\\foo\\bar").endsWith("C:\\foo\\bar")).isTrue();
+ assertThat(path("C:\\foo\\bar").endsWith("C:\\foo")).isFalse();
+ assertThat(path("C:\\foo\\bar").startsWith("foo\\bar")).isFalse();
+ }
+
+ @Test
+ public void testPaths_toAbsolutePath() {
+ assertThatPath(path("C:\\foo\\bar").toAbsolutePath())
+ .isAbsolute()
+ .and()
+ .hasRootComponent("C:\\")
+ .and()
+ .hasNameComponents("foo", "bar")
+ .and()
+ .isEqualTo(path("C:\\foo\\bar"));
+
+ assertThatPath(path("foo\\bar").toAbsolutePath())
+ .isAbsolute()
+ .and()
+ .hasRootComponent("C:\\")
+ .and()
+ .hasNameComponents("work", "foo", "bar")
+ .and()
+ .isEqualTo(path("C:\\work\\foo\\bar"));
+ }
+
+ @Test
+ public void testPaths_toRealPath() throws IOException {
+ Files.createDirectories(path("C:\\foo\\bar"));
+ Files.createSymbolicLink(path("C:\\link"), path("C:\\"));
+
+ assertThatPath(path("C:\\link\\foo\\bar").toRealPath()).isEqualTo(path("C:\\foo\\bar"));
+
+ assertThatPath(path("").toRealPath()).isEqualTo(path("C:\\work"));
+ assertThatPath(path(".").toRealPath()).isEqualTo(path("C:\\work"));
+ assertThatPath(path("..").toRealPath()).isEqualTo(path("C:\\"));
+ assertThatPath(path("..\\..").toRealPath()).isEqualTo(path("C:\\"));
+ assertThatPath(path(".\\..\\.\\..").toRealPath()).isEqualTo(path("C:\\"));
+ assertThatPath(path(".\\..\\.\\..\\.").toRealPath()).isEqualTo(path("C:\\"));
+ }
+
+ @Test
+ public void testPaths_toUri() {
+ assertThat(fs.getPath("C:\\").toUri()).isEqualTo(URI.create("jimfs://win/C:/"));
+ assertThat(fs.getPath("C:\\foo").toUri()).isEqualTo(URI.create("jimfs://win/C:/foo"));
+ assertThat(fs.getPath("C:\\foo\\bar").toUri()).isEqualTo(URI.create("jimfs://win/C:/foo/bar"));
+ assertThat(fs.getPath("foo").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/foo"));
+ assertThat(fs.getPath("foo\\bar").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/foo/bar"));
+ assertThat(fs.getPath("").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/"));
+ assertThat(fs.getPath(".\\..\\.").toUri()).isEqualTo(URI.create("jimfs://win/C:/work/./.././"));
+ }
+
+ @Test
+ public void testPaths_toUri_unc() {
+ assertThat(fs.getPath("\\\\host\\share\\").toUri())
+ .isEqualTo(URI.create("jimfs://win//host/share/"));
+ assertThat(fs.getPath("\\\\host\\share\\foo").toUri())
+ .isEqualTo(URI.create("jimfs://win//host/share/foo"));
+ assertThat(fs.getPath("\\\\host\\share\\foo\\bar").toUri())
+ .isEqualTo(URI.create("jimfs://win//host/share/foo/bar"));
+ }
+
+ @Test
+ public void testPaths_getFromUri() {
+ assertThatPath(Paths.get(URI.create("jimfs://win/C:/"))).isEqualTo(fs.getPath("C:\\"));
+ assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo"))).isEqualTo(fs.getPath("C:\\foo"));
+ assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo%20bar")))
+ .isEqualTo(fs.getPath("C:\\foo bar"));
+ assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo/./bar")))
+ .isEqualTo(fs.getPath("C:\\foo\\.\\bar"));
+ assertThatPath(Paths.get(URI.create("jimfs://win/C:/foo/bar/")))
+ .isEqualTo(fs.getPath("C:\\foo\\bar"));
+ }
+
+ @Test
+ public void testPaths_getFromUri_unc() {
+ assertThatPath(Paths.get(URI.create("jimfs://win//host/share/")))
+ .isEqualTo(fs.getPath("\\\\host\\share\\"));
+ assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo")))
+ .isEqualTo(fs.getPath("\\\\host\\share\\foo"));
+ assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo%20bar")))
+ .isEqualTo(fs.getPath("\\\\host\\share\\foo bar"));
+ assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo/./bar")))
+ .isEqualTo(fs.getPath("\\\\host\\share\\foo\\.\\bar"));
+ assertThatPath(Paths.get(URI.create("jimfs://win//host/share/foo/bar/")))
+ .isEqualTo(fs.getPath("\\\\host\\share\\foo\\bar"));
+ }
+
+ @Test
+ public void testPathMatchers_glob() {
+ assertThatPath("bar").matches("glob:bar");
+ assertThatPath("bar").matches("glob:*");
+ assertThatPath("C:\\foo").doesNotMatch("glob:*");
+ assertThatPath("C:\\foo\\bar").doesNotMatch("glob:*");
+ assertThatPath("C:\\foo\\bar").matches("glob:**");
+ assertThatPath("C:\\foo\\bar").matches("glob:C:\\\\**");
+ assertThatPath("foo\\bar").doesNotMatch("glob:C:\\\\**");
+ assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:\\\\foo\\\\**");
+ assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:\\\\**\\\\stuff");
+ assertThatPath("C:\\foo").matches("glob:C:\\\\[a-z]*");
+ assertThatPath("C:\\Foo").matches("glob:C:\\\\[a-z]*");
+ assertThatPath("C:\\foo").matches("glob:C:\\\\[A-Z]*");
+ assertThatPath("C:\\Foo").matches("glob:C:\\\\[A-Z]*");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**\\\\*.java");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**\\\\*.{java,class}");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.class").matches("glob:**\\\\*.{java,class}");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**\\\\*.*");
+
+ try {
+ fs.getPathMatcher("glob:**\\*.{java,class");
+ fail();
+ } catch (PatternSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testPathMatchers_glob_alternateSeparators() {
+ // only need to test / in the glob pattern; tests above check that / in a path is changed to
+ // \ automatically
+ assertThatPath("C:\\foo").doesNotMatch("glob:*");
+ assertThatPath("C:\\foo\\bar").doesNotMatch("glob:*");
+ assertThatPath("C:\\foo\\bar").matches("glob:**");
+ assertThatPath("C:\\foo\\bar").matches("glob:C:/**");
+ assertThatPath("foo\\bar").doesNotMatch("glob:C:/**");
+ assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:/foo/**");
+ assertThatPath("C:\\foo\\bar\\baz\\stuff").matches("glob:C:/**/stuff");
+ assertThatPath("C:\\foo").matches("glob:C:/[a-z]*");
+ assertThatPath("C:\\Foo").matches("glob:C:/[a-z]*");
+ assertThatPath("C:\\foo").matches("glob:C:/[A-Z]*");
+ assertThatPath("C:\\Foo").matches("glob:C:/[A-Z]*");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**/*.java");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**/*.{java,class}");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.class").matches("glob:**/*.{java,class}");
+ assertThatPath("C:\\foo\\bar\\baz\\Stuff.java").matches("glob:**/*.*");
+
+ try {
+ fs.getPathMatcher("glob:**/*.{java,class");
+ fail();
+ } catch (PatternSyntaxException expected) {
+ }
+ }
+
+ @Test
+ public void testCreateFileOrDirectory_forNonExistentRootPath_fails() throws IOException {
+ try {
+ Files.createDirectory(path("Z:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.createFile(path("Z:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.createSymbolicLink(path("Z:\\"), path("foo"));
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testCopyFile_toNonExistentRootPath_fails() throws IOException {
+ Files.createFile(path("foo"));
+ Files.createDirectory(path("bar"));
+
+ try {
+ Files.copy(path("foo"), path("Z:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.copy(path("bar"), path("Z:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testMoveFile_toNonExistentRootPath_fails() throws IOException {
+ Files.createFile(path("foo"));
+ Files.createDirectory(path("bar"));
+
+ try {
+ Files.move(path("foo"), path("Z:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.move(path("bar"), path("Z:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testDelete_directory_cantDeleteRoot() throws IOException {
+ // test with E:\ because it is empty
+ try {
+ Files.delete(path("E:\\"));
+ fail();
+ } catch (FileSystemException expected) {
+ assertThat(expected.getFile()).isEqualTo("E:\\");
+ assertThat(expected.getMessage()).contains("root");
+ }
+ }
+
+ @Test
+ public void testCreateFileOrDirectory_forExistingRootPath_fails() throws IOException {
+ try {
+ Files.createDirectory(path("E:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.createFile(path("E:\\"));
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.createSymbolicLink(path("E:\\"), path("foo"));
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testCopyFile_toExistingRootPath_fails() throws IOException {
+ Files.createFile(path("foo"));
+ Files.createDirectory(path("bar"));
+
+ try {
+ Files.copy(path("foo"), path("E:\\"), REPLACE_EXISTING);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.copy(path("bar"), path("E:\\"), REPLACE_EXISTING);
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testMoveFile_toExistingRootPath_fails() throws IOException {
+ Files.createFile(path("foo"));
+ Files.createDirectory(path("bar"));
+
+ try {
+ Files.move(path("foo"), path("E:\\"), REPLACE_EXISTING);
+ fail();
+ } catch (IOException expected) {
+ }
+
+ try {
+ Files.move(path("bar"), path("E:\\"), REPLACE_EXISTING);
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ @Test
+ public void testMove_rootDirectory_fails() throws IOException {
+ try {
+ Files.move(path("E:\\"), path("Z:\\"));
+ fail();
+ } catch (FileSystemException expected) {
+ }
+
+ try {
+ Files.move(path("E:\\"), path("C:\\bar"));
+ fail();
+ } catch (FileSystemException expected) {
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/NameTest.java b/jimfs/src/test/java/com/google/common/jimfs/NameTest.java
new file mode 100644
index 0000000..1953076
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/NameTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link Name}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class NameTest {
+
+ @Test
+ public void testNames() {
+ assertThat(Name.create("foo", "foo")).isEqualTo(Name.create("foo", "foo"));
+ assertThat(Name.create("FOO", "foo")).isEqualTo(Name.create("foo", "foo"));
+ assertThat(Name.create("FOO", "foo")).isNotEqualTo(Name.create("FOO", "FOO"));
+
+ assertThat(Name.create("a", "b").toString()).isEqualTo("a");
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/OwnerAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/OwnerAttributeProviderTest.java
new file mode 100644
index 0000000..fc6192b
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/OwnerAttributeProviderTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.UserLookupService.createUserPrincipal;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.attribute.FileOwnerAttributeView;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link OwnerAttributeProvider}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class OwnerAttributeProviderTest
+ extends AbstractAttributeProviderTest<OwnerAttributeProvider> {
+
+ @Override
+ protected OwnerAttributeProvider createProvider() {
+ return new OwnerAttributeProvider();
+ }
+
+ @Override
+ protected Set<? extends AttributeProvider> createInheritedProviders() {
+ return ImmutableSet.of();
+ }
+
+ @Test
+ public void testInitialAttributes() {
+ assertThat(provider.get(file, "owner")).isEqualTo(createUserPrincipal("user"));
+ }
+
+ @Test
+ public void testSet() {
+ assertSetAndGetSucceeds("owner", createUserPrincipal("user"));
+ assertSetFailsOnCreate("owner", createUserPrincipal("user"));
+
+ // invalid type
+ assertSetFails("owner", "root");
+ }
+
+ @Test
+ public void testView() throws IOException {
+ FileOwnerAttributeView view = provider.view(fileLookup(), NO_INHERITED_VIEWS);
+ assertThat(view).isNotNull();
+
+ assertThat(view.name()).isEqualTo("owner");
+ assertThat(view.getOwner()).isEqualTo(createUserPrincipal("user"));
+
+ view.setOwner(createUserPrincipal("root"));
+ assertThat(view.getOwner()).isEqualTo(createUserPrincipal("root"));
+ assertThat(file.getAttribute("owner", "owner")).isEqualTo(createUserPrincipal("root"));
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathNormalizationTest.java b/jimfs/src/test/java/com/google/common/jimfs/PathNormalizationTest.java
new file mode 100644
index 0000000..23b28b4
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/PathNormalizationTest.java
@@ -0,0 +1,351 @@
+/*
+ * 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.PathNormalization.CASE_FOLD_ASCII;
+import static com.google.common.jimfs.PathNormalization.CASE_FOLD_UNICODE;
+import static com.google.common.jimfs.PathNormalization.NFC;
+import static com.google.common.jimfs.PathNormalization.NFD;
+import static com.google.common.jimfs.TestUtils.assertNotEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.regex.Pattern;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link PathNormalization}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class PathNormalizationTest {
+
+ private ImmutableSet<PathNormalization> normalizations;
+
+ @Test
+ public void testNone() {
+ normalizations = ImmutableSet.of();
+
+ assertNormalizedEqual("foo", "foo");
+ assertNormalizedUnequal("Foo", "foo");
+ assertNormalizedUnequal("\u00c5", "\u212b");
+ assertNormalizedUnequal("Am\u00e9lie", "Ame\u0301lie");
+ }
+
+ private static final String[][] CASE_FOLD_TEST_DATA = {
+ {"foo", "fOo", "foO", "Foo", "FOO"},
+ {"efficient", "efficient", "efficient", "Efficient", "EFFICIENT"},
+ {"flour", "flour", "flour", "Flour", "FLOUR"},
+ {"poſt", "post", "poſt", "Poſt", "POST"},
+ {"poſt", "post", "poſt", "Poſt", "POST"},
+ {"ſtop", "stop", "ſtop", "Stop", "STOP"},
+ {"tschüß", "tschüss", "tschüß", "Tschüß", "TSCHÜSS"},
+ {"weiß", "weiss", "weiß", "Weiß", "WEISS"},
+ {"WEIẞ", "weiss", "weiß", "Weiß", "WEIẞ"},
+ {"στιγμας", "στιγμασ", "στιγμας", "Στιγμας", "ΣΤΙΓΜΑΣ"},
+ {"ᾲ στο διάολο", "ὰι στο διάολο", "ᾲ στο διάολο", "Ὰͅ Στο Διάολο", "ᾺΙ ΣΤΟ ΔΙΆΟΛΟ"},
+ {"Henry â…§", "henry â…·", "henry â…·", "Henry â…§", "HENRY â…§"},
+ {"I Work At â“€", "i work at â“š", "i work at â“š", "I Work At â“€", "I WORK AT â“€"},
+ {"ʀᴀʀᴇ", "ʀᴀʀᴇ", "ʀᴀʀᴇ", "Ʀᴀʀᴇ", "ƦᴀƦᴇ"},
+ {"Ὰͅ", "ὰι", "ᾲ", "Ὰͅ", "ᾺΙ"}
+ };
+
+ @Test
+ public void testCaseFold() {
+ normalizations = ImmutableSet.of(CASE_FOLD_UNICODE);
+
+ for (String[] row : CASE_FOLD_TEST_DATA) {
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i; j < row.length; j++) {
+ assertNormalizedEqual(row[i], row[j]);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testCaseInsensitiveAscii() {
+ normalizations = ImmutableSet.of(CASE_FOLD_ASCII);
+
+ String[] row = {"foo", "FOO", "fOo", "Foo"};
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i; j < row.length; j++) {
+ assertNormalizedEqual(row[i], row[j]);
+ }
+ }
+
+ assertNormalizedUnequal("weiß", "weiss");
+ }
+
+ private static final String[][] NORMALIZE_TEST_DATA = {
+ {"\u00c5", "\u212b"}, // two forms of Ã… (one code point each)
+ {"Am\u00e9lie", "Ame\u0301lie"} // two forms of Amélie (one composed, one decomposed)
+ };
+
+ @Test
+ public void testNormalizeNfc() {
+ normalizations = ImmutableSet.of(NFC);
+
+ for (String[] row : NORMALIZE_TEST_DATA) {
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i; j < row.length; j++) {
+ assertNormalizedEqual(row[i], row[j]);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testNormalizeNfd() {
+ normalizations = ImmutableSet.of(NFD);
+
+ for (String[] row : NORMALIZE_TEST_DATA) {
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i; j < row.length; j++) {
+ assertNormalizedEqual(row[i], row[j]);
+ }
+ }
+ }
+ }
+
+ private static final String[][] NORMALIZE_CASE_FOLD_TEST_DATA = {
+ {"\u00c5", "\u00e5", "\u212b"},
+ {"Am\u00e9lie", "Am\u00c9lie", "Ame\u0301lie", "AME\u0301LIE"}
+ };
+
+ @Test
+ public void testNormalizeNfcCaseFold() {
+ normalizations = ImmutableSet.of(NFC, CASE_FOLD_UNICODE);
+
+ for (String[] row : NORMALIZE_CASE_FOLD_TEST_DATA) {
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i; j < row.length; j++) {
+ assertNormalizedEqual(row[i], row[j]);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testNormalizeNfdCaseFold() {
+ normalizations = ImmutableSet.of(NFD, CASE_FOLD_UNICODE);
+
+ for (String[] row : NORMALIZE_CASE_FOLD_TEST_DATA) {
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i; j < row.length; j++) {
+ assertNormalizedEqual(row[i], row[j]);
+ }
+ }
+ }
+ }
+
+ private static final String[][] NORMALIZED_CASE_INSENSITIVE_ASCII_TEST_DATA = {
+ {"\u00e5", "\u212b"},
+ {"Am\u00e9lie", "AME\u0301LIE"}
+ };
+
+ @Test
+ public void testNormalizeNfcCaseFoldAscii() {
+ normalizations = ImmutableSet.of(NFC, CASE_FOLD_ASCII);
+
+ for (String[] row : NORMALIZED_CASE_INSENSITIVE_ASCII_TEST_DATA) {
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i + 1; j < row.length; j++) {
+ assertNormalizedUnequal(row[i], row[j]);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testNormalizeNfdCaseFoldAscii() {
+ normalizations = ImmutableSet.of(NFD, CASE_FOLD_ASCII);
+
+ for (String[] row : NORMALIZED_CASE_INSENSITIVE_ASCII_TEST_DATA) {
+ for (int i = 0; i < row.length; i++) {
+ for (int j = i + 1; j < row.length; j++) {
+ // since decomposition happens before case folding, the strings are equal when the
+ // decomposed ASCII letter is folded
+ assertNormalizedEqual(row[i], row[j]);
+ }
+ }
+ }
+ }
+
+ // regex patterns offer loosely similar matching, but that's all
+
+ @Test
+ public void testNone_pattern() {
+ normalizations = ImmutableSet.of();
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternDoesNotMatch("foo", "FOO");
+ assertNormalizedPatternDoesNotMatch("FOO", "foo");
+ }
+
+ @Test
+ public void testCaseFold_pattern() {
+ normalizations = ImmutableSet.of(CASE_FOLD_UNICODE);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternMatches("foo", "FOO");
+ assertNormalizedPatternMatches("FOO", "foo");
+ assertNormalizedPatternMatches("Am\u00e9lie", "AM\u00c9LIE");
+ assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternDoesNotMatch("AM\u00c9LIE", "AME\u0301LIE");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE");
+ }
+
+ @Test
+ public void testCaseFoldAscii_pattern() {
+ normalizations = ImmutableSet.of(CASE_FOLD_ASCII);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternMatches("foo", "FOO");
+ assertNormalizedPatternMatches("FOO", "foo");
+ assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AM\u00c9LIE");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternDoesNotMatch("AM\u00c9LIE", "AME\u0301LIE");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE");
+ }
+
+ @Test
+ public void testNormalizeNfc_pattern() {
+ normalizations = ImmutableSet.of(NFC);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternDoesNotMatch("foo", "FOO");
+ assertNormalizedPatternDoesNotMatch("FOO", "foo");
+ assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE");
+ }
+
+ @Test
+ public void testNormalizeNfd_pattern() {
+ normalizations = ImmutableSet.of(NFD);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternDoesNotMatch("foo", "FOO");
+ assertNormalizedPatternDoesNotMatch("FOO", "foo");
+ assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AME\u0301LIE");
+ }
+
+ @Test
+ public void testNormalizeNfcCaseFold_pattern() {
+ normalizations = ImmutableSet.of(NFC, CASE_FOLD_UNICODE);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternMatches("foo", "FOO");
+ assertNormalizedPatternMatches("FOO", "foo");
+ assertNormalizedPatternMatches("Am\u00e9lie", "AM\u00c9LIE");
+ assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE");
+ assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE");
+ assertNormalizedPatternMatches("Am\u00e9lie", "AME\u0301LIE");
+ }
+
+ @Test
+ public void testNormalizeNfdCaseFold_pattern() {
+ normalizations = ImmutableSet.of(NFD, CASE_FOLD_UNICODE);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternMatches("foo", "FOO");
+ assertNormalizedPatternMatches("FOO", "foo");
+ assertNormalizedPatternMatches("Am\u00e9lie", "AM\u00c9LIE");
+ assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE");
+ assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE");
+ assertNormalizedPatternMatches("Am\u00e9lie", "AME\u0301LIE");
+ }
+
+ @Test
+ public void testNormalizeNfcCaseFoldAscii_pattern() {
+ normalizations = ImmutableSet.of(NFC, CASE_FOLD_ASCII);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternMatches("foo", "FOO");
+ assertNormalizedPatternMatches("FOO", "foo");
+
+ // these are all a bit fuzzy as when CASE_INSENSITIVE is present but not UNICODE_CASE, ASCII
+ // only strings are expected
+ assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AM\u00c9LIE");
+ assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE");
+ }
+
+ @Test
+ public void testNormalizeNfdCaseFoldAscii_pattern() {
+ normalizations = ImmutableSet.of(NFD, CASE_FOLD_ASCII);
+ assertNormalizedPatternMatches("foo", "foo");
+ assertNormalizedPatternMatches("foo", "FOO");
+ assertNormalizedPatternMatches("FOO", "foo");
+
+ // these are all a bit fuzzy as when CASE_INSENSITIVE is present but not UNICODE_CASE, ASCII
+ // only strings are expected
+ assertNormalizedPatternMatches("Ame\u0301lie", "AME\u0301LIE");
+ assertNormalizedPatternDoesNotMatch("Am\u00e9lie", "AM\u00c9LIE");
+ assertNormalizedPatternMatches("Am\u00e9lie", "Ame\u0301lie");
+ assertNormalizedPatternMatches("AM\u00c9LIE", "AME\u0301LIE");
+ }
+
+ /** Asserts that the given strings normalize to the same string using the current normalizer. */
+ private void assertNormalizedEqual(String first, String second) {
+ assertEquals(
+ PathNormalization.normalize(first, normalizations),
+ PathNormalization.normalize(second, normalizations));
+ }
+
+ /** Asserts that the given strings normalize to different strings using the current normalizer. */
+ private void assertNormalizedUnequal(String first, String second) {
+ assertNotEquals(
+ PathNormalization.normalize(first, normalizations),
+ PathNormalization.normalize(second, normalizations));
+ }
+
+ /**
+ * Asserts that the given strings match when one is compiled as a regex pattern using the current
+ * normalizer and matched against the other.
+ */
+ private void assertNormalizedPatternMatches(String first, String second) {
+ Pattern pattern = PathNormalization.compilePattern(first, normalizations);
+ assertTrue(
+ "pattern '" + pattern + "' does not match '" + second + "'",
+ pattern.matcher(second).matches());
+
+ pattern = PathNormalization.compilePattern(second, normalizations);
+ assertTrue(
+ "pattern '" + pattern + "' does not match '" + first + "'",
+ pattern.matcher(first).matches());
+ }
+
+ /**
+ * Asserts that the given strings do not match when one is compiled as a regex pattern using the
+ * current normalizer and matched against the other.
+ */
+ private void assertNormalizedPatternDoesNotMatch(String first, String second) {
+ Pattern pattern = PathNormalization.compilePattern(first, normalizations);
+ assertFalse(
+ "pattern '" + pattern + "' should not match '" + second + "'",
+ pattern.matcher(second).matches());
+
+ pattern = PathNormalization.compilePattern(second, normalizations);
+ assertFalse(
+ "pattern '" + pattern + "' should not match '" + first + "'",
+ pattern.matcher(first).matches());
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathServiceTest.java b/jimfs/src/test/java/com/google/common/jimfs/PathServiceTest.java
new file mode 100644
index 0000000..65349c7
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/PathServiceTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.PathNormalization.CASE_FOLD_ASCII;
+import static com.google.common.jimfs.PathSubject.paths;
+import static com.google.common.truth.Truth.assertAbout;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.PathMatcher;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link PathService}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class PathServiceTest {
+
+ private static final ImmutableSet<PathNormalization> NO_NORMALIZATIONS = ImmutableSet.of();
+
+ private final PathService service = fakeUnixPathService();
+
+ @Test
+ public void testBasicProperties() {
+ assertThat(service.getSeparator()).isEqualTo("/");
+ assertThat(fakeWindowsPathService().getSeparator()).isEqualTo("\\");
+ }
+
+ @Test
+ public void testPathCreation() {
+ assertAbout(paths())
+ .that(service.emptyPath())
+ .hasRootComponent(null)
+ .and()
+ .hasNameComponents("");
+
+ assertAbout(paths())
+ .that(service.createRoot(service.name("/")))
+ .isAbsolute()
+ .and()
+ .hasRootComponent("/")
+ .and()
+ .hasNoNameComponents();
+
+ assertAbout(paths())
+ .that(service.createFileName(service.name("foo")))
+ .hasRootComponent(null)
+ .and()
+ .hasNameComponents("foo");
+
+ JimfsPath relative = service.createRelativePath(service.names(ImmutableList.of("foo", "bar")));
+ assertAbout(paths())
+ .that(relative)
+ .hasRootComponent(null)
+ .and()
+ .hasNameComponents("foo", "bar");
+
+ JimfsPath absolute =
+ service.createPath(service.name("/"), service.names(ImmutableList.of("foo", "bar")));
+ assertAbout(paths())
+ .that(absolute)
+ .isAbsolute()
+ .and()
+ .hasRootComponent("/")
+ .and()
+ .hasNameComponents("foo", "bar");
+ }
+
+ @Test
+ public void testPathCreation_emptyPath() {
+ // normalized to empty path with single empty string name
+ assertAbout(paths())
+ .that(service.createPath(null, ImmutableList.<Name>of()))
+ .hasRootComponent(null)
+ .and()
+ .hasNameComponents("");
+ }
+
+ @Test
+ public void testPathCreation_parseIgnoresEmptyString() {
+ // if the empty string wasn't ignored, the resulting path would be "/foo" since the empty
+ // string would be joined with foo
+ assertAbout(paths())
+ .that(service.parsePath("", "foo"))
+ .hasRootComponent(null)
+ .and()
+ .hasNameComponents("foo");
+ }
+
+ @Test
+ public void testToString() {
+ // not much to test for this since it just delegates to PathType anyway
+ JimfsPath path =
+ new JimfsPath(service, null, ImmutableList.of(Name.simple("foo"), Name.simple("bar")));
+ assertThat(service.toString(path)).isEqualTo("foo/bar");
+
+ path = new JimfsPath(service, Name.simple("/"), ImmutableList.of(Name.simple("foo")));
+ assertThat(service.toString(path)).isEqualTo("/foo");
+ }
+
+ @Test
+ public void testHash_usingDisplayForm() {
+ PathService pathService = fakePathService(PathType.unix(), false);
+
+ JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("FOO", "foo")));
+ JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("FOO", "FOO")));
+ JimfsPath path3 =
+ new JimfsPath(
+ pathService, null, ImmutableList.of(Name.create("FOO", "9874238974897189741")));
+
+ assertThat(pathService.hash(path1)).isEqualTo(pathService.hash(path2));
+ assertThat(pathService.hash(path2)).isEqualTo(pathService.hash(path3));
+ }
+
+ @Test
+ public void testHash_usingCanonicalForm() {
+ PathService pathService = fakePathService(PathType.unix(), true);
+
+ JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("foo", "foo")));
+ JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("FOO", "foo")));
+ JimfsPath path3 =
+ new JimfsPath(
+ pathService, null, ImmutableList.of(Name.create("28937497189478912374897", "foo")));
+
+ assertThat(pathService.hash(path1)).isEqualTo(pathService.hash(path2));
+ assertThat(pathService.hash(path2)).isEqualTo(pathService.hash(path3));
+ }
+
+ @Test
+ public void testCompareTo_usingDisplayForm() {
+ PathService pathService = fakePathService(PathType.unix(), false);
+
+ JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("a", "z")));
+ JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("b", "y")));
+ JimfsPath path3 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("c", "x")));
+
+ assertThat(pathService.compare(path1, path2)).isEqualTo(-1);
+ assertThat(pathService.compare(path2, path3)).isEqualTo(-1);
+ }
+
+ @Test
+ public void testCompareTo_usingCanonicalForm() {
+ PathService pathService = fakePathService(PathType.unix(), true);
+
+ JimfsPath path1 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("a", "z")));
+ JimfsPath path2 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("b", "y")));
+ JimfsPath path3 = new JimfsPath(pathService, null, ImmutableList.of(Name.create("c", "x")));
+
+ assertThat(pathService.compare(path1, path2)).isEqualTo(1);
+ assertThat(pathService.compare(path2, path3)).isEqualTo(1);
+ }
+
+ @Test
+ public void testPathMatcher() {
+ assertThat(service.createPathMatcher("regex:foo"))
+ .isInstanceOf(PathMatchers.RegexPathMatcher.class);
+ assertThat(service.createPathMatcher("glob:foo"))
+ .isInstanceOf(PathMatchers.RegexPathMatcher.class);
+ }
+
+ @Test
+ public void testPathMatcher_usingCanonicalForm_usesCanonicalNormalizations() {
+ // https://github.com/google/jimfs/issues/91
+ // This matches the behavior of Windows (the only built-in configuration that uses canonical
+ // form for equality). There, PathMatchers should do case-insensitive matching despite Windows
+ // not normalizing case for display.
+ assertCaseInsensitiveMatches(
+ new PathService(
+ PathType.unix(), NO_NORMALIZATIONS, ImmutableSet.of(CASE_FOLD_ASCII), true));
+ assertCaseSensitiveMatches(
+ new PathService(
+ PathType.unix(), ImmutableSet.of(CASE_FOLD_ASCII), NO_NORMALIZATIONS, true));
+ }
+
+ @Test
+ public void testPathMatcher_usingDisplayForm_usesDisplayNormalizations() {
+ assertCaseInsensitiveMatches(
+ new PathService(
+ PathType.unix(), ImmutableSet.of(CASE_FOLD_ASCII), NO_NORMALIZATIONS, false));
+ assertCaseSensitiveMatches(
+ new PathService(
+ PathType.unix(), NO_NORMALIZATIONS, ImmutableSet.of(CASE_FOLD_ASCII), false));
+ }
+
+ private static void assertCaseInsensitiveMatches(PathService service) {
+ ImmutableList<PathMatcher> matchers =
+ ImmutableList.of(
+ service.createPathMatcher("glob:foo"), service.createPathMatcher("glob:FOO"));
+
+ JimfsPath lowerCasePath = singleNamePath(service, "foo");
+ JimfsPath upperCasePath = singleNamePath(service, "FOO");
+ JimfsPath nonMatchingPath = singleNamePath(service, "bar");
+
+ for (PathMatcher matcher : matchers) {
+ assertThat(matcher.matches(lowerCasePath)).isTrue();
+ assertThat(matcher.matches(upperCasePath)).isTrue();
+ assertThat(matcher.matches(nonMatchingPath)).isFalse();
+ }
+ }
+
+ private static void assertCaseSensitiveMatches(PathService service) {
+ PathMatcher matcher = service.createPathMatcher("glob:foo");
+
+ JimfsPath lowerCasePath = singleNamePath(service, "foo");
+ JimfsPath upperCasePath = singleNamePath(service, "FOO");
+
+ assertThat(matcher.matches(lowerCasePath)).isTrue();
+ assertThat(matcher.matches(upperCasePath)).isFalse();
+ }
+
+ public static PathService fakeUnixPathService() {
+ return fakePathService(PathType.unix(), false);
+ }
+
+ public static PathService fakeWindowsPathService() {
+ return fakePathService(PathType.windows(), false);
+ }
+
+ public static PathService fakePathService(PathType type, boolean equalityUsesCanonicalForm) {
+ PathService service =
+ new PathService(type, NO_NORMALIZATIONS, NO_NORMALIZATIONS, equalityUsesCanonicalForm);
+ service.setFileSystem(FILE_SYSTEM);
+ return service;
+ }
+
+ private static JimfsPath singleNamePath(PathService service, String name) {
+ return new JimfsPath(service, null, ImmutableList.of(Name.create(name, name)));
+ }
+
+ private static final FileSystem FILE_SYSTEM;
+
+ static {
+ try {
+ FILE_SYSTEM =
+ JimfsFileSystems.newFileSystem(
+ new JimfsFileSystemProvider(), URI.create("jimfs://foo"), Configuration.unix());
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java b/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java
new file mode 100644
index 0000000..f6927a5
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/PathSubject.java
@@ -0,0 +1,406 @@
+/*
+ * 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.base.Preconditions.checkNotNull;
+import static com.google.common.truth.Fact.fact;
+import static com.google.common.truth.Fact.simpleFact;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Arrays.asList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.BaseEncoding;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+
+/**
+ * Subject for doing assertions on file system paths.
+ *
+ * @author Colin Decker
+ */
+public final class PathSubject extends Subject {
+
+ /** Returns the subject factory for doing assertions on paths. */
+ public static Subject.Factory<PathSubject, Path> paths() {
+ return new PathSubjectFactory();
+ }
+
+ private static final LinkOption[] FOLLOW_LINKS = new LinkOption[0];
+ private static final LinkOption[] NOFOLLOW_LINKS = {LinkOption.NOFOLLOW_LINKS};
+
+ private final Path actual;
+ protected LinkOption[] linkOptions = FOLLOW_LINKS;
+ private Charset charset = UTF_8;
+
+ private PathSubject(FailureMetadata failureMetadata, Path subject) {
+ super(failureMetadata, subject);
+ this.actual = subject;
+ }
+
+ private Path toPath(String path) {
+ return actual.getFileSystem().getPath(path);
+ }
+
+ /** Returns this, for readability of chained assertions. */
+ public PathSubject and() {
+ return this;
+ }
+
+ /** Do not follow links when looking up the path. */
+ public PathSubject noFollowLinks() {
+ this.linkOptions = NOFOLLOW_LINKS;
+ return this;
+ }
+
+ /**
+ * Set the given charset to be used when reading the file at this path as text. Default charset if
+ * not set is UTF-8.
+ */
+ public PathSubject withCharset(Charset charset) {
+ this.charset = checkNotNull(charset);
+ return this;
+ }
+
+ /** Asserts that the path is absolute (it has a root component). */
+ public PathSubject isAbsolute() {
+ if (!actual.isAbsolute()) {
+ failWithActual(simpleFact("expected to be absolute"));
+ }
+ return this;
+ }
+
+ /** Asserts that the path is relative (it has no root component). */
+ public PathSubject isRelative() {
+ if (actual.isAbsolute()) {
+ failWithActual(simpleFact("expected to be relative"));
+ }
+ return this;
+ }
+
+ /** Asserts that the path has the given root component. */
+ public PathSubject hasRootComponent(@NullableDecl String root) {
+ Path rootComponent = actual.getRoot();
+ if (root == null && rootComponent != null) {
+ failWithActual("expected to have root component", root);
+ } else if (root != null && !root.equals(rootComponent.toString())) {
+ failWithActual("expected to have root component", root);
+ }
+ return this;
+ }
+
+ /** Asserts that the path has no name components. */
+ public PathSubject hasNoNameComponents() {
+ check("getNameCount()").that(actual.getNameCount()).isEqualTo(0);
+ return this;
+ }
+
+ /** Asserts that the path has the given name components. */
+ public PathSubject hasNameComponents(String... names) {
+ ImmutableList.Builder<String> builder = ImmutableList.builder();
+ for (Path name : actual) {
+ builder.add(name.toString());
+ }
+
+ if (!builder.build().equals(ImmutableList.copyOf(names))) {
+ failWithActual("expected components", asList(names));
+ }
+ return this;
+ }
+
+ /** Asserts that the path matches the given syntax and pattern. */
+ public PathSubject matches(String syntaxAndPattern) {
+ PathMatcher matcher = actual.getFileSystem().getPathMatcher(syntaxAndPattern);
+ if (!matcher.matches(actual)) {
+ failWithActual("expected to match ", syntaxAndPattern);
+ }
+ return this;
+ }
+
+ /** Asserts that the path does not match the given syntax and pattern. */
+ public PathSubject doesNotMatch(String syntaxAndPattern) {
+ PathMatcher matcher = actual.getFileSystem().getPathMatcher(syntaxAndPattern);
+ if (matcher.matches(actual)) {
+ failWithActual("expected not to match", syntaxAndPattern);
+ }
+ return this;
+ }
+
+ /** Asserts that the path exists. */
+ public PathSubject exists() {
+ if (!Files.exists(actual, linkOptions)) {
+ failWithActual(simpleFact("expected to exist"));
+ }
+ if (Files.notExists(actual, linkOptions)) {
+ failWithActual(simpleFact("expected to exist"));
+ }
+ return this;
+ }
+
+ /** Asserts that the path does not exist. */
+ public PathSubject doesNotExist() {
+ if (!Files.notExists(actual, linkOptions)) {
+ failWithActual(simpleFact("expected not to exist"));
+ }
+ if (Files.exists(actual, linkOptions)) {
+ failWithActual(simpleFact("expected not to exist"));
+ }
+ return this;
+ }
+
+ /** Asserts that the path is a directory. */
+ public PathSubject isDirectory() {
+ exists(); // check for directoryness should imply check for existence
+
+ if (!Files.isDirectory(actual, linkOptions)) {
+ failWithActual(simpleFact("expected to be directory"));
+ }
+ return this;
+ }
+
+ /** Asserts that the path is a regular file. */
+ public PathSubject isRegularFile() {
+ exists(); // check for regular fileness should imply check for existence
+
+ if (!Files.isRegularFile(actual, linkOptions)) {
+ failWithActual(simpleFact("expected to be regular file"));
+ }
+ return this;
+ }
+
+ /** Asserts that the path is a symbolic link. */
+ public PathSubject isSymbolicLink() {
+ exists(); // check for symbolic linkness should imply check for existence
+
+ if (!Files.isSymbolicLink(actual)) {
+ failWithActual(simpleFact("expected to be symbolic link"));
+ }
+ return this;
+ }
+
+ /** Asserts that the path, which is a symbolic link, has the given path as a target. */
+ public PathSubject withTarget(String targetPath) throws IOException {
+ Path actualTarget = Files.readSymbolicLink(actual);
+ if (!actualTarget.equals(toPath(targetPath))) {
+ failWithoutActual(
+ fact("expected link target", targetPath),
+ fact("but target was", actualTarget),
+ fact("for path", actual));
+ }
+ return this;
+ }
+
+ /**
+ * Asserts that the file the path points to exists and has the given number of links to it. Fails
+ * on a file system that does not support the "unix" view.
+ */
+ public PathSubject hasLinkCount(int count) throws IOException {
+ exists();
+
+ int linkCount = (int) Files.getAttribute(actual, "unix:nlink", linkOptions);
+ if (linkCount != count) {
+ failWithActual("expected to have link count", count);
+ }
+ return this;
+ }
+
+ /** Asserts that the path resolves to the same file as the given path. */
+ public PathSubject isSameFileAs(String path) throws IOException {
+ return isSameFileAs(toPath(path));
+ }
+
+ /** Asserts that the path resolves to the same file as the given path. */
+ public PathSubject isSameFileAs(Path path) throws IOException {
+ if (!Files.isSameFile(actual, path)) {
+ failWithActual("expected to be same file as", path);
+ }
+ return this;
+ }
+
+ /** Asserts that the path does not resolve to the same file as the given path. */
+ public PathSubject isNotSameFileAs(String path) throws IOException {
+ if (Files.isSameFile(actual, toPath(path))) {
+ failWithActual("expected not to be same file as", path);
+ }
+ return this;
+ }
+
+ /** Asserts that the directory has no children. */
+ public PathSubject hasNoChildren() throws IOException {
+ isDirectory();
+
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(actual)) {
+ if (stream.iterator().hasNext()) {
+ failWithActual(simpleFact("expected to have no children"));
+ }
+ }
+ return this;
+ }
+
+ /** Asserts that the directory has children with the given names, in the given order. */
+ public PathSubject hasChildren(String... children) throws IOException {
+ isDirectory();
+
+ List<Path> expectedNames = new ArrayList<>();
+ for (String child : children) {
+ expectedNames.add(actual.getFileSystem().getPath(child));
+ }
+
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(actual)) {
+ List<Path> actualNames = new ArrayList<>();
+ for (Path path : stream) {
+ actualNames.add(path.getFileName());
+ }
+
+ if (!actualNames.equals(expectedNames)) {
+ failWithoutActual(
+ fact("expected to have children", expectedNames),
+ fact("but had children", actualNames),
+ fact("for path", actual));
+ }
+ }
+ return this;
+ }
+
+ /** Asserts that the file has the given size. */
+ public PathSubject hasSize(long size) throws IOException {
+ if (Files.size(actual) != size) {
+ failWithActual("expected to have size", size);
+ }
+ return this;
+ }
+
+ /** Asserts that the file is a regular file containing no bytes. */
+ public PathSubject containsNoBytes() throws IOException {
+ return containsBytes(new byte[0]);
+ }
+
+ /**
+ * Asserts that the file is a regular file containing exactly the byte values of the given ints.
+ */
+ public PathSubject containsBytes(int... bytes) throws IOException {
+ byte[] realBytes = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ realBytes[i] = (byte) bytes[i];
+ }
+ return containsBytes(realBytes);
+ }
+
+ /** Asserts that the file is a regular file containing exactly the given bytes. */
+ public PathSubject containsBytes(byte[] bytes) throws IOException {
+ isRegularFile();
+ hasSize(bytes.length);
+
+ byte[] actual = Files.readAllBytes(this.actual);
+ if (!Arrays.equals(bytes, actual)) {
+ System.out.println(BaseEncoding.base16().encode(actual));
+ System.out.println(BaseEncoding.base16().encode(bytes));
+ failWithActual("expected to contain bytes", BaseEncoding.base16().encode(bytes));
+ }
+ return this;
+ }
+
+ /**
+ * Asserts that the file is a regular file containing the same bytes as the regular file at the
+ * given path.
+ */
+ public PathSubject containsSameBytesAs(String path) throws IOException {
+ isRegularFile();
+
+ byte[] expectedBytes = Files.readAllBytes(toPath(path));
+ if (!Arrays.equals(expectedBytes, Files.readAllBytes(actual))) {
+ failWithActual("expected to contain same bytes as", path);
+ }
+ return this;
+ }
+
+ /**
+ * Asserts that the file is a regular file containing the given lines of text. By default, the
+ * bytes are decoded as UTF-8; for a different charset, use {@link #withCharset(Charset)}.
+ */
+ public PathSubject containsLines(String... lines) throws IOException {
+ return containsLines(Arrays.asList(lines));
+ }
+
+ /**
+ * Asserts that the file is a regular file containing the given lines of text. By default, the
+ * bytes are decoded as UTF-8; for a different charset, use {@link #withCharset(Charset)}.
+ */
+ public PathSubject containsLines(Iterable<String> lines) throws IOException {
+ isRegularFile();
+
+ List<String> expected = ImmutableList.copyOf(lines);
+ List<String> actual = Files.readAllLines(this.actual, charset);
+ check("lines()").that(actual).isEqualTo(expected);
+ return this;
+ }
+
+ /** Returns an object for making assertions about the given attribute. */
+ public Attribute attribute(final String attribute) {
+ return new Attribute() {
+ @Override
+ public Attribute is(Object value) throws IOException {
+ Object actualValue = Files.getAttribute(actual, attribute, linkOptions);
+ check("attribute(%s)", attribute).that(actualValue).isEqualTo(value);
+ return this;
+ }
+
+ @Override
+ public Attribute isNot(Object value) throws IOException {
+ Object actualValue = Files.getAttribute(actual, attribute, linkOptions);
+ check("attribute(%s)", attribute).that(actualValue).isNotEqualTo(value);
+ return this;
+ }
+
+ @Override
+ public PathSubject and() {
+ return PathSubject.this;
+ }
+ };
+ }
+
+ private static class PathSubjectFactory implements Subject.Factory<PathSubject, Path> {
+
+ @Override
+ public PathSubject createSubject(FailureMetadata failureMetadata, Path that) {
+ return new PathSubject(failureMetadata, that);
+ }
+ }
+
+ /** Interface for assertions about a file attribute. */
+ public interface Attribute {
+
+ /** Asserts that the value of this attribute is equal to the given value. */
+ Attribute is(Object value) throws IOException;
+
+ /** Asserts that the value of this attribute is not equal to the given value. */
+ Attribute isNot(Object value) throws IOException;
+
+ /** Returns the path subject for further chaining. */
+ PathSubject and();
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathTester.java b/jimfs/src/test/java/com/google/common/jimfs/PathTester.java
new file mode 100644
index 0000000..b96d77e
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/PathTester.java
@@ -0,0 +1,187 @@
+/*
+ * 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.base.Functions.toStringFunction;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.List;
+
+/** @author Colin Decker */
+public final class PathTester {
+
+ private final PathService pathService;
+ private final String string;
+ private String root;
+ private ImmutableList<String> names = ImmutableList.of();
+
+ public PathTester(PathService pathService, String string) {
+ this.pathService = pathService;
+ this.string = string;
+ }
+
+ public PathTester root(String root) {
+ this.root = root;
+ return this;
+ }
+
+ public PathTester names(Iterable<String> names) {
+ this.names = ImmutableList.copyOf(names);
+ return this;
+ }
+
+ public PathTester names(String... names) {
+ return names(Arrays.asList(names));
+ }
+
+ public void test(String first, String... more) {
+ Path path = pathService.parsePath(first, more);
+ test(path);
+ }
+
+ public void test(Path path) {
+ assertEquals(string, path.toString());
+
+ testRoot(path);
+ testNames(path);
+ testParents(path);
+ testStartsWith(path);
+ testEndsWith(path);
+ testSubpaths(path);
+ }
+
+ private void testRoot(Path path) {
+ if (root != null) {
+ assertTrue(path + ".isAbsolute() should be true", path.isAbsolute());
+ assertNotNull(path + ".getRoot() should not be null", path.getRoot());
+ assertEquals(root, path.getRoot().toString());
+ } else {
+ assertFalse(path + ".isAbsolute() should be false", path.isAbsolute());
+ assertNull(path + ".getRoot() should be null", path.getRoot());
+ }
+ }
+
+ private void testNames(Path path) {
+ assertEquals(names.size(), path.getNameCount());
+ assertEquals(names, names(path));
+ for (int i = 0; i < names.size(); i++) {
+ assertEquals(names.get(i), path.getName(i).toString());
+ // don't test individual names if this is an individual name
+ if (names.size() > 1) {
+ new PathTester(pathService, names.get(i)).names(names.get(i)).test(path.getName(i));
+ }
+ }
+ if (names.size() > 0) {
+ String fileName = names.get(names.size() - 1);
+ assertEquals(fileName, path.getFileName().toString());
+ // don't test individual names if this is an individual name
+ if (names.size() > 1) {
+ new PathTester(pathService, fileName).names(fileName).test(path.getFileName());
+ }
+ }
+ }
+
+ private void testParents(Path path) {
+ Path parent = path.getParent();
+
+ if (root != null && names.size() >= 1 || names.size() > 1) {
+ assertNotNull(parent);
+ }
+
+ if (parent != null) {
+ String parentName = names.size() == 1 ? root : string.substring(0, string.lastIndexOf('/'));
+ new PathTester(pathService, parentName)
+ .root(root)
+ .names(names.subList(0, names.size() - 1))
+ .test(parent);
+ }
+ }
+
+ private void testSubpaths(Path path) {
+ if (path.getRoot() == null) {
+ assertEquals(path, path.subpath(0, path.getNameCount()));
+ }
+
+ if (path.getNameCount() > 1) {
+ String stringWithoutRoot = root == null ? string : string.substring(root.length());
+
+ // test start + 1 to end and start to end - 1 subpaths... this recursively tests all subpaths
+ // actually tests most possible subpaths multiple times but... eh
+ Path startSubpath = path.subpath(1, path.getNameCount());
+ List<String> startNames =
+ ImmutableList.copyOf(Splitter.on('/').split(stringWithoutRoot))
+ .subList(1, path.getNameCount());
+
+ new PathTester(pathService, Joiner.on('/').join(startNames))
+ .names(startNames)
+ .test(startSubpath);
+
+ Path endSubpath = path.subpath(0, path.getNameCount() - 1);
+ List<String> endNames =
+ ImmutableList.copyOf(Splitter.on('/').split(stringWithoutRoot))
+ .subList(0, path.getNameCount() - 1);
+
+ new PathTester(pathService, Joiner.on('/').join(endNames)).names(endNames).test(endSubpath);
+ }
+ }
+
+ private void testStartsWith(Path path) {
+ // empty path doesn't start with any path
+ if (root != null || !names.isEmpty()) {
+ Path other = path;
+ while (other != null) {
+ assertTrue(path + ".startsWith(" + other + ") should be true", path.startsWith(other));
+ assertTrue(
+ path + ".startsWith(" + other + ") should be true", path.startsWith(other.toString()));
+ other = other.getParent();
+ }
+ }
+ }
+
+ private void testEndsWith(Path path) {
+ // empty path doesn't start with any path
+ if (root != null || !names.isEmpty()) {
+ Path other = path;
+ while (other != null) {
+ assertTrue(path + ".endsWith(" + other + ") should be true", path.endsWith(other));
+ assertTrue(
+ path + ".endsWith(" + other + ") should be true", path.endsWith(other.toString()));
+ if (other.getRoot() != null && other.getNameCount() > 0) {
+ other = other.subpath(0, other.getNameCount());
+ } else if (other.getNameCount() > 1) {
+ other = other.subpath(1, other.getNameCount());
+ } else {
+ other = null;
+ }
+ }
+ }
+ }
+
+ private static List<String> names(Path path) {
+ return FluentIterable.from(path).transform(toStringFunction()).toList();
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java b/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java
new file mode 100644
index 0000000..59fc114
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/PathTypeTest.java
@@ -0,0 +1,157 @@
+/*
+ * 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.base.Preconditions.checkArgument;
+import static com.google.common.jimfs.PathType.ParseResult;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import java.net.URI;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link PathType}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class PathTypeTest {
+
+ private static final FakePathType type = new FakePathType();
+ static final URI fileSystemUri = URI.create("jimfs://foo");
+
+ @Test
+ public void testBasicProperties() {
+ assertThat(type.getSeparator()).isEqualTo("/");
+ assertThat(type.getOtherSeparators()).isEqualTo("\\");
+ }
+
+ @Test
+ public void testParsePath() {
+ ParseResult path = type.parsePath("foo/bar/baz/one\\two");
+ assertParseResult(path, null, "foo", "bar", "baz", "one", "two");
+
+ ParseResult path2 = type.parsePath("$one//\\two");
+ assertParseResult(path2, "$", "one", "two");
+ }
+
+ @Test
+ public void testToString() {
+ ParseResult path = type.parsePath("foo/bar\\baz");
+ assertThat(type.toString(path.root(), path.names())).isEqualTo("foo/bar/baz");
+
+ ParseResult path2 = type.parsePath("$/foo/bar");
+ assertThat(type.toString(path2.root(), path2.names())).isEqualTo("$foo/bar");
+ }
+
+ @Test
+ public void testToUri() {
+ URI fileUri = type.toUri(fileSystemUri, "$", ImmutableList.of("foo", "bar"), false);
+ assertThat(fileUri.toString()).isEqualTo("jimfs://foo/$/foo/bar");
+ assertThat(fileUri.getPath()).isEqualTo("/$/foo/bar");
+
+ URI directoryUri = type.toUri(fileSystemUri, "$", ImmutableList.of("foo", "bar"), true);
+ assertThat(directoryUri.toString()).isEqualTo("jimfs://foo/$/foo/bar/");
+ assertThat(directoryUri.getPath()).isEqualTo("/$/foo/bar/");
+
+ URI rootUri = type.toUri(fileSystemUri, "$", ImmutableList.<String>of(), true);
+ assertThat(rootUri.toString()).isEqualTo("jimfs://foo/$/");
+ assertThat(rootUri.getPath()).isEqualTo("/$/");
+ }
+
+ @Test
+ public void testToUri_escaping() {
+ URI fileUri = type.toUri(fileSystemUri, "$", ImmutableList.of("foo", "bar baz"), false);
+ assertThat(fileUri.toString()).isEqualTo("jimfs://foo/$/foo/bar%20baz");
+ assertThat(fileUri.getRawPath()).isEqualTo("/$/foo/bar%20baz");
+ assertThat(fileUri.getPath()).isEqualTo("/$/foo/bar baz");
+ }
+
+ @Test
+ public void testUriRoundTrips() {
+ assertUriRoundTripsCorrectly(type, "$");
+ assertUriRoundTripsCorrectly(type, "$foo");
+ assertUriRoundTripsCorrectly(type, "$foo/bar/baz");
+ assertUriRoundTripsCorrectly(type, "$foo bar");
+ assertUriRoundTripsCorrectly(type, "$foo/bar baz");
+ }
+
+ static void assertParseResult(ParseResult result, @NullableDecl String root, String... names) {
+ assertThat(result.root()).isEqualTo(root);
+ assertThat(result.names()).containsExactly((Object[]) names).inOrder();
+ }
+
+ static void assertUriRoundTripsCorrectly(PathType type, String path) {
+ ParseResult result = type.parsePath(path);
+ URI uri = type.toUri(fileSystemUri, result.root(), result.names(), false);
+ ParseResult parsedUri = type.fromUri(uri);
+ assertThat(parsedUri.root()).isEqualTo(result.root());
+ assertThat(parsedUri.names()).containsExactlyElementsIn(result.names()).inOrder();
+ }
+
+ /** Arbitrary path type with $ as the root, / as the separator and \ as an alternate separator. */
+ private static final class FakePathType extends PathType {
+
+ protected FakePathType() {
+ super(false, '/', '\\');
+ }
+
+ @Override
+ public ParseResult parsePath(String path) {
+ String root = null;
+ if (path.startsWith("$")) {
+ root = "$";
+ path = path.substring(1);
+ }
+
+ return new ParseResult(root, splitter().split(path));
+ }
+
+ @Override
+ public String toString(@NullableDecl String root, Iterable<String> names) {
+ StringBuilder builder = new StringBuilder();
+ if (root != null) {
+ builder.append(root);
+ }
+ joiner().appendTo(builder, names);
+ return builder.toString();
+ }
+
+ @Override
+ public String toUriPath(String root, Iterable<String> names, boolean directory) {
+ StringBuilder builder = new StringBuilder();
+ builder.append('/').append(root);
+ for (String name : names) {
+ builder.append('/').append(name);
+ }
+ if (directory) {
+ builder.append('/');
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public ParseResult parseUriPath(String uriPath) {
+ checkArgument(uriPath.startsWith("/$"), "uriPath (%s) must start with /$", uriPath);
+ return parsePath(uriPath.substring(1));
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java b/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java
new file mode 100644
index 0000000..86f9832
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/PollingWatchServiceTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.jimfs.AbstractWatchService.Event;
+import com.google.common.jimfs.AbstractWatchService.Key;
+import com.google.common.util.concurrent.Runnables;
+import com.google.common.util.concurrent.Uninterruptibles;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.NotDirectoryException;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link PollingWatchService}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class PollingWatchServiceTest {
+
+ private JimfsFileSystem fs;
+ private PollingWatchService watcher;
+
+ @Before
+ public void setUp() {
+ fs = (JimfsFileSystem) Jimfs.newFileSystem(Configuration.unix());
+ watcher =
+ new PollingWatchService(
+ fs.getDefaultView(),
+ fs.getPathService(),
+ new FileSystemState(Runnables.doNothing()),
+ 4,
+ MILLISECONDS);
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ watcher.close();
+ fs.close();
+ watcher = null;
+ fs = null;
+ }
+
+ @Test
+ public void testNewWatcher() {
+ assertThat(watcher.isOpen()).isTrue();
+ assertThat(watcher.isPolling()).isFalse();
+ }
+
+ @Test
+ public void testRegister() throws IOException {
+ Key key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
+ assertThat(key.isValid()).isTrue();
+
+ assertThat(watcher.isPolling()).isTrue();
+ }
+
+ @Test
+ public void testRegister_fileDoesNotExist() throws IOException {
+ try {
+ watcher.register(fs.getPath("/a/b/c"), ImmutableList.of(ENTRY_CREATE));
+ fail();
+ } catch (NoSuchFileException expected) {
+ }
+ }
+
+ @Test
+ public void testRegister_fileIsNotDirectory() throws IOException {
+ Path path = fs.getPath("/a.txt");
+ Files.createFile(path);
+ try {
+ watcher.register(path, ImmutableList.of(ENTRY_CREATE));
+ fail();
+ } catch (NotDirectoryException expected) {
+ }
+ }
+
+ @Test
+ public void testCancellingLastKeyStopsPolling() throws IOException {
+ Key key = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
+ key.cancel();
+ assertThat(key.isValid()).isFalse();
+
+ assertThat(watcher.isPolling()).isFalse();
+
+ Key key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
+ Key key3 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE));
+
+ assertThat(watcher.isPolling()).isTrue();
+
+ key2.cancel();
+
+ assertThat(watcher.isPolling()).isTrue();
+
+ key3.cancel();
+
+ assertThat(watcher.isPolling()).isFalse();
+ }
+
+ @Test
+ public void testCloseCancelsAllKeysAndStopsPolling() throws IOException {
+ Key key1 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_CREATE));
+ Key key2 = watcher.register(createDirectory(), ImmutableList.of(ENTRY_DELETE));
+
+ assertThat(key1.isValid()).isTrue();
+ assertThat(key2.isValid()).isTrue();
+ assertThat(watcher.isPolling()).isTrue();
+
+ watcher.close();
+
+ assertThat(key1.isValid()).isFalse();
+ assertThat(key2.isValid()).isFalse();
+ assertThat(watcher.isPolling()).isFalse();
+ }
+
+ @Test(timeout = 2000)
+ public void testWatchForOneEventType() throws IOException, InterruptedException {
+ JimfsPath path = createDirectory();
+ watcher.register(path, ImmutableList.of(ENTRY_CREATE));
+
+ Files.createFile(path.resolve("foo"));
+
+ assertWatcherHasEvents(new Event<>(ENTRY_CREATE, 1, fs.getPath("foo")));
+
+ Files.createFile(path.resolve("bar"));
+ Files.createFile(path.resolve("baz"));
+
+ assertWatcherHasEvents(
+ new Event<>(ENTRY_CREATE, 1, fs.getPath("bar")),
+ new Event<>(ENTRY_CREATE, 1, fs.getPath("baz")));
+ }
+
+ @Test(timeout = 2000)
+ public void testWatchForMultipleEventTypes() throws IOException, InterruptedException {
+ JimfsPath path = createDirectory();
+ watcher.register(path, ImmutableList.of(ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY));
+
+ Files.createDirectory(path.resolve("foo"));
+ Files.createFile(path.resolve("bar"));
+
+ assertWatcherHasEvents(
+ new Event<>(ENTRY_CREATE, 1, fs.getPath("bar")),
+ new Event<>(ENTRY_CREATE, 1, fs.getPath("foo")));
+
+ Files.createFile(path.resolve("baz"));
+ Files.delete(path.resolve("bar"));
+ Files.createFile(path.resolve("foo/bar"));
+
+ assertWatcherHasEvents(
+ new Event<>(ENTRY_CREATE, 1, fs.getPath("baz")),
+ new Event<>(ENTRY_DELETE, 1, fs.getPath("bar")),
+ new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")));
+
+ Files.delete(path.resolve("foo/bar"));
+ ensureTimeToPoll(); // watcher polls, seeing modification, then polls again, seeing delete
+ Files.delete(path.resolve("foo"));
+
+ assertWatcherHasEvents(
+ new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")),
+ new Event<>(ENTRY_DELETE, 1, fs.getPath("foo")));
+
+ Files.createDirectories(path.resolve("foo/bar"));
+
+ // polling here may either see just the creation of foo, or may first see the creation of foo
+ // and then the creation of foo/bar (modification of foo) since those don't happen atomically
+ assertWatcherHasEvents(
+ ImmutableList.<WatchEvent<?>>of(new Event<>(ENTRY_CREATE, 1, fs.getPath("foo"))),
+ // or
+ ImmutableList.<WatchEvent<?>>of(
+ new Event<>(ENTRY_CREATE, 1, fs.getPath("foo")),
+ new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo"))));
+
+ Files.delete(path.resolve("foo/bar"));
+ Files.delete(path.resolve("foo"));
+
+ // polling here may either just see the deletion of foo, or may first see the deletion of bar
+ // (modification of foo) and then the deletion of foo
+ assertWatcherHasEvents(
+ ImmutableList.<WatchEvent<?>>of(new Event<>(ENTRY_DELETE, 1, fs.getPath("foo"))),
+ // or
+ ImmutableList.<WatchEvent<?>>of(
+ new Event<>(ENTRY_MODIFY, 1, fs.getPath("foo")),
+ new Event<>(ENTRY_DELETE, 1, fs.getPath("foo"))));
+ }
+
+ private void assertWatcherHasEvents(WatchEvent<?>... events) throws InterruptedException {
+ assertWatcherHasEvents(Arrays.asList(events), ImmutableList.<WatchEvent<?>>of());
+ }
+
+ private void assertWatcherHasEvents(List<WatchEvent<?>> expected, List<WatchEvent<?>> alternate)
+ throws InterruptedException {
+ ensureTimeToPoll(); // otherwise we could read 1 event but not all the events we're expecting
+ WatchKey key = watcher.take();
+ List<WatchEvent<?>> keyEvents = key.pollEvents();
+
+ if (keyEvents.size() == expected.size() || alternate.isEmpty()) {
+ assertThat(keyEvents).containsExactlyElementsIn(expected);
+ } else {
+ assertThat(keyEvents).containsExactlyElementsIn(alternate);
+ }
+ key.reset();
+ }
+
+ private static void ensureTimeToPoll() {
+ Uninterruptibles.sleepUninterruptibly(40, MILLISECONDS);
+ }
+
+ private JimfsPath createDirectory() throws IOException {
+ JimfsPath path = fs.getPath("/" + UUID.randomUUID().toString());
+ Files.createDirectory(path);
+ return path;
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/PosixAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/PosixAttributeProviderTest.java
new file mode 100644
index 0000000..baef1f9
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/PosixAttributeProviderTest.java
@@ -0,0 +1,124 @@
+/*
+ * 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.UserLookupService.createGroupPrincipal;
+import static com.google.common.jimfs.UserLookupService.createUserPrincipal;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFileAttributes;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link PosixAttributeProvider}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class PosixAttributeProviderTest
+ extends AbstractAttributeProviderTest<PosixAttributeProvider> {
+
+ @Override
+ protected PosixAttributeProvider createProvider() {
+ return new PosixAttributeProvider();
+ }
+
+ @Override
+ protected Set<? extends AttributeProvider> createInheritedProviders() {
+ return ImmutableSet.of(new BasicAttributeProvider(), new OwnerAttributeProvider());
+ }
+
+ @Test
+ public void testInitialAttributes() {
+ assertContainsAll(
+ file,
+ ImmutableMap.of(
+ "group", createGroupPrincipal("group"),
+ "permissions", PosixFilePermissions.fromString("rw-r--r--")));
+ }
+
+ @Test
+ public void testSet() {
+ assertSetAndGetSucceeds("group", createGroupPrincipal("foo"));
+ assertSetAndGetSucceeds("permissions", PosixFilePermissions.fromString("rwxrwxrwx"));
+
+ // invalid types
+ assertSetFails("permissions", ImmutableList.of(PosixFilePermission.GROUP_EXECUTE));
+ assertSetFails("permissions", ImmutableSet.of("foo"));
+ }
+
+ @Test
+ public void testSetOnCreate() {
+ assertSetAndGetSucceedsOnCreate("permissions", PosixFilePermissions.fromString("rwxrwxrwx"));
+ assertSetFailsOnCreate("group", createGroupPrincipal("foo"));
+ }
+
+ @Test
+ public void testView() throws IOException {
+ file.setAttribute("owner", "owner", createUserPrincipal("user"));
+
+ PosixFileAttributeView view =
+ provider.view(
+ fileLookup(),
+ ImmutableMap.of(
+ "basic", new BasicAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS),
+ "owner", new OwnerAttributeProvider().view(fileLookup(), NO_INHERITED_VIEWS)));
+ assertNotNull(view);
+
+ assertThat(view.name()).isEqualTo("posix");
+ assertThat(view.getOwner()).isEqualTo(createUserPrincipal("user"));
+
+ PosixFileAttributes attrs = view.readAttributes();
+ assertThat(attrs.fileKey()).isEqualTo(0);
+ assertThat(attrs.owner()).isEqualTo(createUserPrincipal("user"));
+ assertThat(attrs.group()).isEqualTo(createGroupPrincipal("group"));
+ assertThat(attrs.permissions()).isEqualTo(PosixFilePermissions.fromString("rw-r--r--"));
+
+ view.setOwner(createUserPrincipal("root"));
+ assertThat(view.getOwner()).isEqualTo(createUserPrincipal("root"));
+ assertThat(file.getAttribute("owner", "owner")).isEqualTo(createUserPrincipal("root"));
+
+ view.setGroup(createGroupPrincipal("root"));
+ assertThat(view.readAttributes().group()).isEqualTo(createGroupPrincipal("root"));
+ assertThat(file.getAttribute("posix", "group")).isEqualTo(createGroupPrincipal("root"));
+
+ view.setPermissions(PosixFilePermissions.fromString("rwx------"));
+ assertThat(view.readAttributes().permissions())
+ .isEqualTo(PosixFilePermissions.fromString("rwx------"));
+ assertThat(file.getAttribute("posix", "permissions"))
+ .isEqualTo(PosixFilePermissions.fromString("rwx------"));
+ }
+
+ @Test
+ public void testAttributes() {
+ PosixFileAttributes attrs = provider.readAttributes(file);
+ assertThat(attrs.permissions()).isEqualTo(PosixFilePermissions.fromString("rw-r--r--"));
+ assertThat(attrs.group()).isEqualTo(createGroupPrincipal("group"));
+ assertThat(attrs.fileKey()).isEqualTo(0);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/RegexGlobMatcherTest.java b/jimfs/src/test/java/com/google/common/jimfs/RegexGlobMatcherTest.java
new file mode 100644
index 0000000..5836e78
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/RegexGlobMatcherTest.java
@@ -0,0 +1,101 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.PathMatcher;
+import java.util.regex.Pattern;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link PathMatcher} instances created by {@link GlobToRegex}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class RegexGlobMatcherTest extends AbstractGlobMatcherTest {
+
+ @Override
+ protected PathMatcher matcher(String pattern) {
+ return PathMatchers.getPathMatcher(
+ "glob:" + pattern, "/", ImmutableSet.<PathNormalization>of());
+ }
+
+ @Override
+ protected PathMatcher realMatcher(String pattern) {
+ FileSystem defaultFileSystem = FileSystems.getDefault();
+ if ("/".equals(defaultFileSystem.getSeparator())) {
+ return defaultFileSystem.getPathMatcher("glob:" + pattern);
+ }
+ return null;
+ }
+
+ @Test
+ public void testRegexTranslation() {
+ assertGlobRegexIs("foo", "foo");
+ assertGlobRegexIs("/", "/");
+ assertGlobRegexIs("?", "[^/]");
+ assertGlobRegexIs("*", "[^/]*");
+ assertGlobRegexIs("**", ".*");
+ assertGlobRegexIs("/foo", "/foo");
+ assertGlobRegexIs("?oo", "[^/]oo");
+ assertGlobRegexIs("*oo", "[^/]*oo");
+ assertGlobRegexIs("**/*.java", ".*/[^/]*\\.java");
+ assertGlobRegexIs("[a-z]", "[[^/]&&[a-z]]");
+ assertGlobRegexIs("[!a-z]", "[[^/]&&[^a-z]]");
+ assertGlobRegexIs("[-a-z]", "[[^/]&&[-a-z]]");
+ assertGlobRegexIs("[!-a-z]", "[[^/]&&[^-a-z]]");
+ assertGlobRegexIs("{a,b,c}", "(a|b|c)");
+ assertGlobRegexIs("{?oo,[A-Z]*,foo/**}", "([^/]oo|[[^/]&&[A-Z]][^/]*|foo/.*)");
+ }
+
+ @Test
+ public void testRegexEscaping() {
+ assertGlobRegexIs("(", "\\(");
+ assertGlobRegexIs(".", "\\.");
+ assertGlobRegexIs("^", "\\^");
+ assertGlobRegexIs("$", "\\$");
+ assertGlobRegexIs("+", "\\+");
+ assertGlobRegexIs("\\\\", "\\\\");
+ assertGlobRegexIs("]", "\\]");
+ assertGlobRegexIs(")", "\\)");
+ assertGlobRegexIs("}", "\\}");
+ }
+
+ @Test
+ public void testRegexTranslationWithMultipleSeparators() {
+ assertGlobRegexIs("?", "[^\\\\/]", "\\/");
+ assertGlobRegexIs("*", "[^\\\\/]*", "\\/");
+ assertGlobRegexIs("/", "[\\\\/]", "\\/");
+ assertGlobRegexIs("\\\\", "[\\\\/]", "\\/");
+ }
+
+ private static void assertGlobRegexIs(String glob, String regex) {
+ assertGlobRegexIs(glob, regex, "/");
+ }
+
+ private static void assertGlobRegexIs(String glob, String regex, String separators) {
+ assertEquals(regex, GlobToRegex.toRegex(glob, separators));
+ Pattern.compile(regex); // ensure the regex syntax is valid
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java b/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java
new file mode 100644
index 0000000..1dc9df2
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/RegularFileBlocksTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.truth.Truth.assertThat;
+
+import com.google.common.primitives.Bytes;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the lower-level operations dealing with the blocks of a {@link RegularFile}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class RegularFileBlocksTest {
+
+ private RegularFile file;
+
+ @Before
+ public void setUp() {
+ file = createFile();
+ }
+
+ private static RegularFile createFile() {
+ return RegularFile.create(-1, new HeapDisk(2, 2, 2));
+ }
+
+ @Test
+ public void testInitialState() {
+ assertThat(file.blockCount()).isEqualTo(0);
+
+ // no bounds checking, but there should never be a block at an index >= size
+ assertThat(file.getBlock(0)).isNull();
+ }
+
+ @Test
+ public void testAddAndGet() {
+ file.addBlock(new byte[] {1});
+
+ assertThat(file.blockCount()).isEqualTo(1);
+ assertThat(Bytes.asList(file.getBlock(0))).isEqualTo(Bytes.asList(new byte[] {1}));
+ assertThat(file.getBlock(1)).isNull();
+
+ file.addBlock(new byte[] {1, 2});
+
+ assertThat(file.blockCount()).isEqualTo(2);
+ assertThat(Bytes.asList(file.getBlock(1))).isEqualTo(Bytes.asList(new byte[] {1, 2}));
+ assertThat(file.getBlock(2)).isNull();
+ }
+
+ @Test
+ public void testTruncate() {
+ file.addBlock(new byte[0]);
+ file.addBlock(new byte[0]);
+ file.addBlock(new byte[0]);
+ file.addBlock(new byte[0]);
+
+ assertThat(file.blockCount()).isEqualTo(4);
+
+ file.truncateBlocks(2);
+
+ assertThat(file.blockCount()).isEqualTo(2);
+ assertThat(file.getBlock(2)).isNull();
+ assertThat(file.getBlock(3)).isNull();
+ assertThat(file.getBlock(0)).isNotNull();
+
+ file.truncateBlocks(0);
+ assertThat(file.blockCount()).isEqualTo(0);
+ assertThat(file.getBlock(0)).isNull();
+ }
+
+ @Test
+ public void testCopyTo() {
+ file.addBlock(new byte[] {1});
+ file.addBlock(new byte[] {1, 2});
+ RegularFile other = createFile();
+
+ assertThat(other.blockCount()).isEqualTo(0);
+
+ file.copyBlocksTo(other, 2);
+
+ assertThat(other.blockCount()).isEqualTo(2);
+ assertThat(other.getBlock(0)).isEqualTo(file.getBlock(0));
+ assertThat(other.getBlock(1)).isEqualTo(file.getBlock(1));
+
+ file.copyBlocksTo(other, 1); // should copy the last block
+
+ assertThat(other.blockCount()).isEqualTo(3);
+ assertThat(other.getBlock(2)).isEqualTo(file.getBlock(1));
+
+ other.copyBlocksTo(file, 3);
+
+ assertThat(file.blockCount()).isEqualTo(5);
+ assertThat(file.getBlock(2)).isEqualTo(other.getBlock(0));
+ assertThat(file.getBlock(3)).isEqualTo(other.getBlock(1));
+ assertThat(file.getBlock(4)).isEqualTo(other.getBlock(2));
+ }
+
+ @Test
+ public void testTransferTo() {
+ file.addBlock(new byte[] {1});
+ file.addBlock(new byte[] {1, 2});
+ file.addBlock(new byte[] {1, 2, 3});
+ RegularFile other = createFile();
+
+ assertThat(file.blockCount()).isEqualTo(3);
+ assertThat(other.blockCount()).isEqualTo(0);
+
+ file.transferBlocksTo(other, 3);
+
+ assertThat(file.blockCount()).isEqualTo(0);
+ assertThat(other.blockCount()).isEqualTo(3);
+
+ assertThat(file.getBlock(0)).isNull();
+ assertThat(Bytes.asList(other.getBlock(0))).isEqualTo(Bytes.asList(new byte[] {1}));
+ assertThat(Bytes.asList(other.getBlock(1))).isEqualTo(Bytes.asList(new byte[] {1, 2}));
+ assertThat(Bytes.asList(other.getBlock(2))).isEqualTo(Bytes.asList(new byte[] {1, 2, 3}));
+
+ other.transferBlocksTo(file, 1);
+
+ assertThat(file.blockCount()).isEqualTo(1);
+ assertThat(other.blockCount()).isEqualTo(2);
+ assertThat(other.getBlock(2)).isNull();
+ assertThat(Bytes.asList(file.getBlock(0))).isEqualTo(Bytes.asList(new byte[] {1, 2, 3}));
+ assertThat(file.getBlock(1)).isNull();
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java b/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java
new file mode 100644
index 0000000..f581690
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/RegularFileTest.java
@@ -0,0 +1,892 @@
+/*
+ * 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.buffer;
+import static com.google.common.jimfs.TestUtils.buffers;
+import static com.google.common.jimfs.TestUtils.bytes;
+import static com.google.common.primitives.Bytes.concat;
+import static org.junit.Assert.assertArrayEquals;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Tests for {@link RegularFile} and by extension for {@link HeapDisk}. These tests test files
+ * created by a heap disk in a number of different states.
+ *
+ * @author Colin Decker
+ */
+public class RegularFileTest {
+
+ /**
+ * Returns a test suite for testing file methods with a variety of {@code HeapDisk}
+ * configurations.
+ */
+ public static TestSuite suite() {
+ TestSuite suite = new TestSuite();
+
+ for (ReuseStrategy reuseStrategy : EnumSet.allOf(ReuseStrategy.class)) {
+ TestSuite suiteForReuseStrategy = new TestSuite(reuseStrategy.toString());
+ Set<List<Integer>> sizeOptions =
+ Sets.cartesianProduct(ImmutableList.of(BLOCK_SIZES, CACHE_SIZES));
+ for (List<Integer> options : sizeOptions) {
+ int blockSize = options.get(0);
+ int cacheSize = options.get(1);
+ if (cacheSize > 0 && cacheSize < blockSize) {
+ // skip cases where the cache size is not -1 (all) or 0 (none) but it is < blockSize,
+ // because this is equivalent to a cache size of 0
+ continue;
+ }
+
+ TestConfiguration state = new TestConfiguration(blockSize, cacheSize, reuseStrategy);
+ TestSuite suiteForTest = new TestSuite(state.toString());
+ for (Method method : TEST_METHODS) {
+ RegularFileTestRunner tester = new RegularFileTestRunner(method.getName(), state);
+ suiteForTest.addTest(tester);
+ }
+ suiteForReuseStrategy.addTest(suiteForTest);
+ }
+ suite.addTest(suiteForReuseStrategy);
+ }
+
+ return suite;
+ }
+
+ public static final ImmutableSet<Integer> BLOCK_SIZES = ImmutableSet.of(2, 8, 128, 8192);
+ public static final ImmutableSet<Integer> CACHE_SIZES = ImmutableSet.of(0, 4, 16, 128, -1);
+
+ private static final ImmutableList<Method> TEST_METHODS =
+ FluentIterable.from(Arrays.asList(RegularFileTestRunner.class.getDeclaredMethods()))
+ .filter(
+ new Predicate<Method>() {
+ @Override
+ public boolean apply(Method method) {
+ return method.getName().startsWith("test")
+ && Modifier.isPublic(method.getModifiers())
+ && method.getParameterTypes().length == 0;
+ }
+ })
+ .toList();
+
+ /**
+ * Different strategies for handling reuse of disks and/or files between tests, intended to ensure
+ * that {@link HeapDisk} operates properly in a variety of usage states including newly created,
+ * having created files that have not been deleted yet, having created files that have been
+ * deleted, and having created files some of which have been deleted and some of which have not.
+ */
+ public enum ReuseStrategy {
+ /** Creates a new disk for each test. */
+ NEW_DISK,
+ /** Retains files after each test, forcing new blocks to be allocated. */
+ KEEP_FILES,
+ /** Deletes files after each test, allowing caching to be used if enabled. */
+ DELETE_FILES,
+ /** Randomly keeps or deletes a file after each test. */
+ KEEP_OR_DELETE_FILES
+ }
+
+ /** Configuration for a set of test cases. */
+ public static final class TestConfiguration {
+
+ private final int blockSize;
+ private final int cacheSize;
+ private final ReuseStrategy reuseStrategy;
+
+ private HeapDisk disk;
+
+ public TestConfiguration(int blockSize, int cacheSize, ReuseStrategy reuseStrategy) {
+ this.blockSize = blockSize;
+ this.cacheSize = cacheSize;
+ this.reuseStrategy = reuseStrategy;
+
+ if (reuseStrategy != ReuseStrategy.NEW_DISK) {
+ this.disk = createDisk();
+ }
+ }
+
+ private HeapDisk createDisk() {
+ int maxCachedBlockCount = cacheSize == -1 ? Integer.MAX_VALUE : (cacheSize / blockSize);
+ return new HeapDisk(blockSize, Integer.MAX_VALUE, maxCachedBlockCount);
+ }
+
+ public RegularFile createRegularFile() {
+ if (reuseStrategy == ReuseStrategy.NEW_DISK) {
+ disk = createDisk();
+ }
+ return RegularFile.create(0, disk);
+ }
+
+ public void tearDown(RegularFile file) {
+ switch (reuseStrategy) {
+ case DELETE_FILES:
+ file.deleted();
+ break;
+ case KEEP_OR_DELETE_FILES:
+ if (new Random().nextBoolean()) {
+ file.deleted();
+ }
+ break;
+ case KEEP_FILES:
+ break;
+ default:
+ break;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return reuseStrategy + " [" + blockSize + ", " + cacheSize + "]";
+ }
+ }
+
+ /** Actual test cases for testing RegularFiles. */
+ public static class RegularFileTestRunner extends TestCase {
+
+ private final TestConfiguration configuration;
+
+ protected RegularFile file;
+
+ public RegularFileTestRunner(String methodName, TestConfiguration configuration) {
+ super(methodName);
+ this.configuration = configuration;
+ }
+
+ @Override
+ public String getName() {
+ return super.getName() + " [" + configuration + "]";
+ }
+
+ @Override
+ public void setUp() {
+ file = configuration.createRegularFile();
+ }
+
+ @Override
+ public void tearDown() {
+ configuration.tearDown(file);
+ }
+
+ private void fillContent(String fill) throws IOException {
+ file.write(0, buffer(fill));
+ }
+
+ public void testEmpty() {
+ assertEquals(0, file.size());
+ assertContentEquals("", file);
+ }
+
+ public void testEmpty_read_singleByte() {
+ assertEquals(-1, file.read(0));
+ assertEquals(-1, file.read(1));
+ }
+
+ public void testEmpty_read_byteArray() {
+ byte[] array = new byte[10];
+ assertEquals(-1, file.read(0, array, 0, array.length));
+ assertArrayEquals(bytes("0000000000"), array);
+ }
+
+ public void testEmpty_read_singleBuffer() {
+ ByteBuffer buffer = ByteBuffer.allocate(10);
+ int read = file.read(0, buffer);
+ assertEquals(-1, read);
+ assertEquals(0, buffer.position());
+ }
+
+ public void testEmpty_read_multipleBuffers() {
+ ByteBuffer buf1 = ByteBuffer.allocate(5);
+ ByteBuffer buf2 = ByteBuffer.allocate(5);
+ long read = file.read(0, ImmutableList.of(buf1, buf2));
+ assertEquals(-1, read);
+ assertEquals(0, buf1.position());
+ assertEquals(0, buf2.position());
+ }
+
+ public void testEmpty_write_singleByte_atStart() throws IOException {
+ file.write(0, (byte) 1);
+ assertContentEquals("1", file);
+ }
+
+ public void testEmpty_write_byteArray_atStart() throws IOException {
+ byte[] bytes = bytes("111111");
+ file.write(0, bytes, 0, bytes.length);
+ assertContentEquals(bytes, file);
+ }
+
+ public void testEmpty_write_partialByteArray_atStart() throws IOException {
+ byte[] bytes = bytes("2211111122");
+ file.write(0, bytes, 2, 6);
+ assertContentEquals("111111", file);
+ }
+
+ public void testEmpty_write_singleBuffer_atStart() throws IOException {
+ file.write(0, buffer("111111"));
+ assertContentEquals("111111", file);
+ }
+
+ public void testEmpty_write_multipleBuffers_atStart() throws IOException {
+ file.write(0, buffers("111", "111"));
+ assertContentEquals("111111", file);
+ }
+
+ public void testEmpty_write_singleByte_atNonZeroPosition() throws IOException {
+ file.write(5, (byte) 1);
+ assertContentEquals("000001", file);
+ }
+
+ public void testEmpty_write_byteArray_atNonZeroPosition() throws IOException {
+ byte[] bytes = bytes("111111");
+ file.write(5, bytes, 0, bytes.length);
+ assertContentEquals("00000111111", file);
+ }
+
+ public void testEmpty_write_partialByteArray_atNonZeroPosition() throws IOException {
+ byte[] bytes = bytes("2211111122");
+ file.write(5, bytes, 2, 6);
+ assertContentEquals("00000111111", file);
+ }
+
+ public void testEmpty_write_singleBuffer_atNonZeroPosition() throws IOException {
+ file.write(5, buffer("111"));
+ assertContentEquals("00000111", file);
+ }
+
+ public void testEmpty_write_multipleBuffers_atNonZeroPosition() throws IOException {
+ file.write(5, buffers("111", "222"));
+ assertContentEquals("00000111222", file);
+ }
+
+ public void testEmpty_write_noBytesArray_atStart() throws IOException {
+ file.write(0, bytes(), 0, 0);
+ assertContentEquals(bytes(), file);
+ }
+
+ public void testEmpty_write_noBytesArray_atNonZeroPosition() throws IOException {
+ file.write(5, bytes(), 0, 0);
+ assertContentEquals(bytes("00000"), file);
+ }
+
+ public void testEmpty_write_noBytesBuffer_atStart() throws IOException {
+ file.write(0, buffer(""));
+ assertContentEquals(bytes(), file);
+ }
+
+ public void testEmpty_write_noBytesBuffer_atNonZeroPosition() throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(0);
+ file.write(5, buffer);
+ assertContentEquals(bytes("00000"), file);
+ }
+
+ public void testEmpty_write_noBytesBuffers_atStart() throws IOException {
+ file.write(0, ImmutableList.of(buffer(""), buffer(""), buffer("")));
+ assertContentEquals(bytes(), file);
+ }
+
+ public void testEmpty_write_noBytesBuffers_atNonZeroPosition() throws IOException {
+ file.write(5, ImmutableList.of(buffer(""), buffer(""), buffer("")));
+ assertContentEquals(bytes("00000"), file);
+ }
+
+ public void testEmpty_transferFrom_fromStart_countEqualsSrcSize() throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 0, 6);
+ assertEquals(6, transferred);
+ assertContentEquals("111111", file);
+ }
+
+ public void testEmpty_transferFrom_fromStart_countLessThanSrcSize() throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 0, 3);
+ assertEquals(3, transferred);
+ assertContentEquals("111", file);
+ }
+
+ public void testEmpty_transferFrom_fromStart_countGreaterThanSrcSize() throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 0, 12);
+ assertEquals(6, transferred);
+ assertContentEquals("111111", file);
+ }
+
+ public void testEmpty_transferFrom_fromBeyondStart_countEqualsSrcSize() throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 6);
+ assertEquals(6, transferred);
+ assertContentEquals("0000111111", file);
+ }
+
+ public void testEmpty_transferFrom_fromBeyondStart_countLessThanSrcSize() throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 3);
+ assertEquals(3, transferred);
+ assertContentEquals("0000111", file);
+ }
+
+ public void testEmpty_transferFrom_fromBeyondStart_countGreaterThanSrcSize()
+ throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("111111")), 4, 12);
+ assertEquals(6, transferred);
+ assertContentEquals("0000111111", file);
+ }
+
+ public void testEmpty_transferFrom_fromStart_noBytes_countEqualsSrcSize() throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 0, 0);
+ assertEquals(0, transferred);
+ assertContentEquals(bytes(), file);
+ }
+
+ public void testEmpty_transferFrom_fromStart_noBytes_countGreaterThanSrcSize()
+ throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 0, 10);
+ assertEquals(0, transferred);
+ assertContentEquals(bytes(), file);
+ }
+
+ public void testEmpty_transferFrom_fromBeyondStart_noBytes_countEqualsSrcSize()
+ throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 5, 0);
+ assertEquals(0, transferred);
+ assertContentEquals(bytes("00000"), file);
+ }
+
+ public void testEmpty_transferFrom_fromBeyondStart_noBytes_countGreaterThanSrcSize()
+ throws IOException {
+ long transferred = file.transferFrom(new ByteBufferChannel(buffer("")), 5, 10);
+ assertEquals(0, transferred);
+ assertContentEquals(bytes("00000"), file);
+ }
+
+ public void testEmpty_transferTo() throws IOException {
+ ByteBufferChannel channel = new ByteBufferChannel(100);
+ assertEquals(0, file.transferTo(0, 100, channel));
+ }
+
+ public void testEmpty_copy() throws IOException {
+ RegularFile copy = file.copyWithoutContent(1);
+ assertContentEquals("", copy);
+ }
+
+ public void testEmpty_truncate_toZero() throws IOException {
+ file.truncate(0);
+ assertContentEquals("", file);
+ }
+
+ public void testEmpty_truncate_sizeUp() throws IOException {
+ file.truncate(10);
+ assertContentEquals("", file);
+ }
+
+ public void testNonEmpty() throws IOException {
+ fillContent("222222");
+ assertContentEquals("222222", file);
+ }
+
+ public void testNonEmpty_read_singleByte() throws IOException {
+ fillContent("123456");
+ assertEquals(1, file.read(0));
+ assertEquals(2, file.read(1));
+ assertEquals(6, file.read(5));
+ assertEquals(-1, file.read(6));
+ assertEquals(-1, file.read(100));
+ }
+
+ public void testNonEmpty_read_all_byteArray() throws IOException {
+ fillContent("222222");
+ byte[] array = new byte[6];
+ assertEquals(6, file.read(0, array, 0, array.length));
+ assertArrayEquals(bytes("222222"), array);
+ }
+
+ public void testNonEmpty_read_all_singleBuffer() throws IOException {
+ fillContent("222222");
+ ByteBuffer buffer = ByteBuffer.allocate(6);
+ assertEquals(6, file.read(0, buffer));
+ assertBufferEquals("222222", 0, buffer);
+ }
+
+ public void testNonEmpty_read_all_multipleBuffers() throws IOException {
+ fillContent("223334");
+ ByteBuffer buf1 = ByteBuffer.allocate(3);
+ ByteBuffer buf2 = ByteBuffer.allocate(3);
+ assertEquals(6, file.read(0, ImmutableList.of(buf1, buf2)));
+ assertBufferEquals("223", 0, buf1);
+ assertBufferEquals("334", 0, buf2);
+ }
+
+ public void testNonEmpty_read_all_byteArray_largerThanContent() throws IOException {
+ fillContent("222222");
+ byte[] array = new byte[10];
+ assertEquals(6, file.read(0, array, 0, array.length));
+ assertArrayEquals(bytes("2222220000"), array);
+ array = new byte[10];
+ assertEquals(6, file.read(0, array, 2, 6));
+ assertArrayEquals(bytes("0022222200"), array);
+ }
+
+ public void testNonEmpty_read_all_singleBuffer_largerThanContent() throws IOException {
+ fillContent("222222");
+ ByteBuffer buffer = ByteBuffer.allocate(16);
+ assertBufferEquals("0000000000000000", 16, buffer);
+ assertEquals(6, file.read(0, buffer));
+ assertBufferEquals("2222220000000000", 10, buffer);
+ }
+
+ public void testNonEmpty_read_all_multipleBuffers_largerThanContent() throws IOException {
+ fillContent("222222");
+ ByteBuffer buf1 = ByteBuffer.allocate(4);
+ ByteBuffer buf2 = ByteBuffer.allocate(8);
+ assertEquals(6, file.read(0, ImmutableList.of(buf1, buf2)));
+ assertBufferEquals("2222", 0, buf1);
+ assertBufferEquals("22000000", 6, buf2);
+ }
+
+ public void testNonEmpty_read_all_multipleBuffers_extraBuffers() throws IOException {
+ fillContent("222222");
+ ByteBuffer buf1 = ByteBuffer.allocate(4);
+ ByteBuffer buf2 = ByteBuffer.allocate(8);
+ ByteBuffer buf3 = ByteBuffer.allocate(4);
+ assertEquals(6, file.read(0, ImmutableList.of(buf1, buf2, buf3)));
+ assertBufferEquals("2222", 0, buf1);
+ assertBufferEquals("22000000", 6, buf2);
+ assertBufferEquals("0000", 4, buf3);
+ }
+
+ public void testNonEmpty_read_partial_fromStart_byteArray() throws IOException {
+ fillContent("222222");
+ byte[] array = new byte[3];
+ assertEquals(3, file.read(0, array, 0, array.length));
+ assertArrayEquals(bytes("222"), array);
+ array = new byte[10];
+ assertEquals(3, file.read(0, array, 1, 3));
+ assertArrayEquals(bytes("0222000000"), array);
+ }
+
+ public void testNonEmpty_read_partial_fromMiddle_byteArray() throws IOException {
+ fillContent("22223333");
+ byte[] array = new byte[3];
+ assertEquals(3, file.read(3, array, 0, array.length));
+ assertArrayEquals(bytes("233"), array);
+ array = new byte[10];
+ assertEquals(3, file.read(3, array, 1, 3));
+ assertArrayEquals(bytes("0233000000"), array);
+ }
+
+ public void testNonEmpty_read_partial_fromEnd_byteArray() throws IOException {
+ fillContent("2222222222");
+ byte[] array = new byte[3];
+ assertEquals(2, file.read(8, array, 0, array.length));
+ assertArrayEquals(bytes("220"), array);
+ array = new byte[10];
+ assertEquals(2, file.read(8, array, 1, 3));
+ assertArrayEquals(bytes("0220000000"), array);
+ }
+
+ public void testNonEmpty_read_partial_fromStart_singleBuffer() throws IOException {
+ fillContent("222222");
+ ByteBuffer buffer = ByteBuffer.allocate(3);
+ assertEquals(3, file.read(0, buffer));
+ assertBufferEquals("222", 0, buffer);
+ }
+
+ public void testNonEmpty_read_partial_fromMiddle_singleBuffer() throws IOException {
+ fillContent("22223333");
+ ByteBuffer buffer = ByteBuffer.allocate(3);
+ assertEquals(3, file.read(3, buffer));
+ assertBufferEquals("233", 0, buffer);
+ }
+
+ public void testNonEmpty_read_partial_fromEnd_singleBuffer() throws IOException {
+ fillContent("2222222222");
+ ByteBuffer buffer = ByteBuffer.allocate(3);
+ assertEquals(2, file.read(8, buffer));
+ assertBufferEquals("220", 1, buffer);
+ }
+
+ public void testNonEmpty_read_partial_fromStart_multipleBuffers() throws IOException {
+ fillContent("12345678");
+ ByteBuffer buf1 = ByteBuffer.allocate(2);
+ ByteBuffer buf2 = ByteBuffer.allocate(2);
+ assertEquals(4, file.read(0, ImmutableList.of(buf1, buf2)));
+ assertBufferEquals("12", 0, buf1);
+ assertBufferEquals("34", 0, buf2);
+ }
+
+ public void testNonEmpty_read_partial_fromMiddle_multipleBuffers() throws IOException {
+ fillContent("12345678");
+ ByteBuffer buf1 = ByteBuffer.allocate(2);
+ ByteBuffer buf2 = ByteBuffer.allocate(2);
+ assertEquals(4, file.read(3, ImmutableList.of(buf1, buf2)));
+ assertBufferEquals("45", 0, buf1);
+ assertBufferEquals("67", 0, buf2);
+ }
+
+ public void testNonEmpty_read_partial_fromEnd_multipleBuffers() throws IOException {
+ fillContent("123456789");
+ ByteBuffer buf1 = ByteBuffer.allocate(2);
+ ByteBuffer buf2 = ByteBuffer.allocate(2);
+ assertEquals(3, file.read(6, ImmutableList.of(buf1, buf2)));
+ assertBufferEquals("78", 0, buf1);
+ assertBufferEquals("90", 1, buf2);
+ }
+
+ public void testNonEmpty_read_fromPastEnd_byteArray() throws IOException {
+ fillContent("123");
+ byte[] array = new byte[3];
+ assertEquals(-1, file.read(3, array, 0, array.length));
+ assertArrayEquals(bytes("000"), array);
+ assertEquals(-1, file.read(3, array, 0, 2));
+ assertArrayEquals(bytes("000"), array);
+ }
+
+ public void testNonEmpty_read_fromPastEnd_singleBuffer() throws IOException {
+ fillContent("123");
+ ByteBuffer buffer = ByteBuffer.allocate(3);
+ file.read(3, buffer);
+ assertBufferEquals("000", 3, buffer);
+ }
+
+ public void testNonEmpty_read_fromPastEnd_multipleBuffers() throws IOException {
+ fillContent("123");
+ ByteBuffer buf1 = ByteBuffer.allocate(2);
+ ByteBuffer buf2 = ByteBuffer.allocate(2);
+ assertEquals(-1, file.read(6, ImmutableList.of(buf1, buf2)));
+ assertBufferEquals("00", 2, buf1);
+ assertBufferEquals("00", 2, buf2);
+ }
+
+ public void testNonEmpty_write_partial_fromStart_singleByte() throws IOException {
+ fillContent("222222");
+ assertEquals(1, file.write(0, (byte) 1));
+ assertContentEquals("122222", file);
+ }
+
+ public void testNonEmpty_write_partial_fromMiddle_singleByte() throws IOException {
+ fillContent("222222");
+ assertEquals(1, file.write(3, (byte) 1));
+ assertContentEquals("222122", file);
+ }
+
+ public void testNonEmpty_write_partial_fromEnd_singleByte() throws IOException {
+ fillContent("222222");
+ assertEquals(1, file.write(6, (byte) 1));
+ assertContentEquals("2222221", file);
+ }
+
+ public void testNonEmpty_write_partial_fromStart_byteArray() throws IOException {
+ fillContent("222222");
+ assertEquals(3, file.write(0, bytes("111"), 0, 3));
+ assertContentEquals("111222", file);
+ assertEquals(2, file.write(0, bytes("333333"), 0, 2));
+ assertContentEquals("331222", file);
+ }
+
+ public void testNonEmpty_write_partial_fromMiddle_byteArray() throws IOException {
+ fillContent("22222222");
+ assertEquals(3, file.write(3, buffer("111")));
+ assertContentEquals("22211122", file);
+ assertEquals(2, file.write(5, bytes("333333"), 1, 2));
+ assertContentEquals("22211332", file);
+ }
+
+ public void testNonEmpty_write_partial_fromBeforeEnd_byteArray() throws IOException {
+ fillContent("22222222");
+ assertEquals(3, file.write(6, bytes("111"), 0, 3));
+ assertContentEquals("222222111", file);
+ assertEquals(2, file.write(8, bytes("333333"), 2, 2));
+ assertContentEquals("2222221133", file);
+ }
+
+ public void testNonEmpty_write_partial_fromEnd_byteArray() throws IOException {
+ fillContent("222222");
+ assertEquals(3, file.write(6, bytes("111"), 0, 3));
+ assertContentEquals("222222111", file);
+ assertEquals(2, file.write(9, bytes("333333"), 3, 2));
+ assertContentEquals("22222211133", file);
+ }
+
+ public void testNonEmpty_write_partial_fromPastEnd_byteArray() throws IOException {
+ fillContent("222222");
+ assertEquals(3, file.write(8, bytes("111"), 0, 3));
+ assertContentEquals("22222200111", file);
+ assertEquals(2, file.write(13, bytes("333333"), 4, 2));
+ assertContentEquals("222222001110033", file);
+ }
+
+ public void testNonEmpty_write_partial_fromStart_singleBuffer() throws IOException {
+ fillContent("222222");
+ assertEquals(3, file.write(0, buffer("111")));
+ assertContentEquals("111222", file);
+ }
+
+ public void testNonEmpty_write_partial_fromMiddle_singleBuffer() throws IOException {
+ fillContent("22222222");
+ assertEquals(3, file.write(3, buffer("111")));
+ assertContentEquals("22211122", file);
+ }
+
+ public void testNonEmpty_write_partial_fromBeforeEnd_singleBuffer() throws IOException {
+ fillContent("22222222");
+ assertEquals(3, file.write(6, buffer("111")));
+ assertContentEquals("222222111", file);
+ }
+
+ public void testNonEmpty_write_partial_fromEnd_singleBuffer() throws IOException {
+ fillContent("222222");
+ assertEquals(3, file.write(6, buffer("111")));
+ assertContentEquals("222222111", file);
+ }
+
+ public void testNonEmpty_write_partial_fromPastEnd_singleBuffer() throws IOException {
+ fillContent("222222");
+ assertEquals(3, file.write(8, buffer("111")));
+ assertContentEquals("22222200111", file);
+ }
+
+ public void testNonEmpty_write_partial_fromStart_multipleBuffers() throws IOException {
+ fillContent("222222");
+ assertEquals(4, file.write(0, buffers("11", "33")));
+ assertContentEquals("113322", file);
+ }
+
+ public void testNonEmpty_write_partial_fromMiddle_multipleBuffers() throws IOException {
+ fillContent("22222222");
+ assertEquals(4, file.write(2, buffers("11", "33")));
+ assertContentEquals("22113322", file);
+ }
+
+ public void testNonEmpty_write_partial_fromBeforeEnd_multipleBuffers() throws IOException {
+ fillContent("22222222");
+ assertEquals(6, file.write(6, buffers("111", "333")));
+ assertContentEquals("222222111333", file);
+ }
+
+ public void testNonEmpty_write_partial_fromEnd_multipleBuffers() throws IOException {
+ fillContent("222222");
+ assertEquals(6, file.write(6, buffers("111", "333")));
+ assertContentEquals("222222111333", file);
+ }
+
+ public void testNonEmpty_write_partial_fromPastEnd_multipleBuffers() throws IOException {
+ fillContent("222222");
+ assertEquals(4, file.write(10, buffers("11", "33")));
+ assertContentEquals("22222200001133", file);
+ }
+
+ public void testNonEmpty_write_overwrite_sameLength() throws IOException {
+ fillContent("2222");
+ assertEquals(4, file.write(0, buffer("1234")));
+ assertContentEquals("1234", file);
+ }
+
+ public void testNonEmpty_write_overwrite_greaterLength() throws IOException {
+ fillContent("2222");
+ assertEquals(8, file.write(0, buffer("12345678")));
+ assertContentEquals("12345678", file);
+ }
+
+ public void testNonEmpty_transferTo_fromStart_countEqualsSize() throws IOException {
+ fillContent("123456");
+ ByteBufferChannel channel = new ByteBufferChannel(10);
+ assertEquals(6, file.transferTo(0, 6, channel));
+ assertBufferEquals("1234560000", 4, channel.buffer());
+ }
+
+ public void testNonEmpty_transferTo_fromStart_countLessThanSize() throws IOException {
+ fillContent("123456");
+ ByteBufferChannel channel = new ByteBufferChannel(10);
+ assertEquals(4, file.transferTo(0, 4, channel));
+ assertBufferEquals("1234000000", 6, channel.buffer());
+ }
+
+ public void testNonEmpty_transferTo_fromMiddle_countEqualsSize() throws IOException {
+ fillContent("123456");
+ ByteBufferChannel channel = new ByteBufferChannel(10);
+ assertEquals(2, file.transferTo(4, 6, channel));
+ assertBufferEquals("5600000000", 8, channel.buffer());
+ }
+
+ public void testNonEmpty_transferTo_fromMiddle_countLessThanSize() throws IOException {
+ fillContent("12345678");
+ ByteBufferChannel channel = new ByteBufferChannel(10);
+ assertEquals(4, file.transferTo(3, 4, channel));
+ assertBufferEquals("4567000000", 6, channel.buffer());
+ }
+
+ public void testNonEmpty_transferFrom_toStart_countEqualsSrcSize() throws IOException {
+ fillContent("22222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("11111"));
+ assertEquals(5, file.transferFrom(channel, 0, 5));
+ assertContentEquals("11111222", file);
+ }
+
+ public void testNonEmpty_transferFrom_toStart_countLessThanSrcSize() throws IOException {
+ fillContent("22222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("11111"));
+ assertEquals(3, file.transferFrom(channel, 0, 3));
+ assertContentEquals("11122222", file);
+ }
+
+ public void testNonEmpty_transferFrom_toStart_countGreaterThanSrcSize() throws IOException {
+ fillContent("22222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("11111"));
+ assertEquals(5, file.transferFrom(channel, 0, 10));
+ assertContentEquals("11111222", file);
+ }
+
+ public void testNonEmpty_transferFrom_toMiddle_countEqualsSrcSize() throws IOException {
+ fillContent("22222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("1111"));
+ assertEquals(4, file.transferFrom(channel, 2, 4));
+ assertContentEquals("22111122", file);
+ }
+
+ public void testNonEmpty_transferFrom_toMiddle_countLessThanSrcSize() throws IOException {
+ fillContent("22222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("11111"));
+ assertEquals(3, file.transferFrom(channel, 2, 3));
+ assertContentEquals("22111222", file);
+ }
+
+ public void testNonEmpty_transferFrom_toMiddle_countGreaterThanSrcSize() throws IOException {
+ fillContent("22222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("1111"));
+ assertEquals(4, file.transferFrom(channel, 2, 100));
+ assertContentEquals("22111122", file);
+ }
+
+ public void testNonEmpty_transferFrom_toMiddle_transferGoesBeyondContentSize()
+ throws IOException {
+ fillContent("222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("111111"));
+ assertEquals(6, file.transferFrom(channel, 4, 6));
+ assertContentEquals("2222111111", file);
+ }
+
+ public void testNonEmpty_transferFrom_toEnd() throws IOException {
+ fillContent("222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("111111"));
+ assertEquals(6, file.transferFrom(channel, 6, 6));
+ assertContentEquals("222222111111", file);
+ }
+
+ public void testNonEmpty_transferFrom_toPastEnd() throws IOException {
+ fillContent("222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("111111"));
+ assertEquals(6, file.transferFrom(channel, 10, 6));
+ assertContentEquals("2222220000111111", file);
+ }
+
+ public void testNonEmpty_transferFrom_hugeOverestimateCount() throws IOException {
+ fillContent("222222");
+ ByteBufferChannel channel = new ByteBufferChannel(buffer("111111"));
+ assertEquals(6, file.transferFrom(channel, 6, 1024 * 1024 * 10));
+ assertContentEquals("222222111111", file);
+ }
+
+ public void testNonEmpty_copy() throws IOException {
+ fillContent("123456");
+ RegularFile copy = file.copyWithoutContent(1);
+ file.copyContentTo(copy);
+ assertContentEquals("123456", copy);
+ }
+
+ public void testNonEmpty_copy_multipleTimes() throws IOException {
+ fillContent("123456");
+ RegularFile copy = file.copyWithoutContent(1);
+ file.copyContentTo(copy);
+ RegularFile copy2 = copy.copyWithoutContent(2);
+ copy.copyContentTo(copy2);
+ assertContentEquals("123456", copy);
+ }
+
+ public void testNonEmpty_truncate_toZero() throws IOException {
+ fillContent("123456");
+ file.truncate(0);
+ assertContentEquals("", file);
+ }
+
+ public void testNonEmpty_truncate_partial() throws IOException {
+ fillContent("12345678");
+ file.truncate(5);
+ assertContentEquals("12345", file);
+ }
+
+ public void testNonEmpty_truncate_sizeUp() throws IOException {
+ fillContent("123456");
+ file.truncate(12);
+ assertContentEquals("123456", file);
+ }
+
+ public void testDeletedStoreRemainsUsableWhileOpen() throws IOException {
+ byte[] bytes = bytes("1234567890");
+ file.write(0, bytes, 0, bytes.length);
+
+ file.opened();
+ file.opened();
+
+ file.deleted();
+
+ assertContentEquals(bytes, file);
+
+ byte[] moreBytes = bytes("1234");
+ file.write(bytes.length, moreBytes, 0, 4);
+
+ byte[] totalBytes = concat(bytes, bytes("1234"));
+ assertContentEquals(totalBytes, file);
+
+ file.closed();
+
+ assertContentEquals(totalBytes, file);
+
+ file.closed();
+
+ // don't check anything else; no guarantee of what if anything will happen once the file is
+ // deleted and completely closed
+ }
+
+ private static void assertBufferEquals(String expected, ByteBuffer actual) {
+ assertEquals(expected.length(), actual.capacity());
+ assertArrayEquals(bytes(expected), actual.array());
+ }
+
+ private static void assertBufferEquals(String expected, int remaining, ByteBuffer actual) {
+ assertBufferEquals(expected, actual);
+ assertEquals(remaining, actual.remaining());
+ }
+
+ private static void assertContentEquals(String expected, RegularFile actual) {
+ assertContentEquals(bytes(expected), actual);
+ }
+
+ protected static void assertContentEquals(byte[] expected, RegularFile actual) {
+ assertEquals(expected.length, actual.sizeWithoutLocking());
+ byte[] actualBytes = new byte[(int) actual.sizeWithoutLocking()];
+ actual.read(0, ByteBuffer.wrap(actualBytes));
+ assertArrayEquals(expected, actualBytes);
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java b/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java
new file mode 100644
index 0000000..4518132
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/TestAttributeProvider.java
@@ -0,0 +1,227 @@
+/*
+ * 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.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.util.HashMap;
+import java.util.Map;
+import org.checkerframework.checker.nullness.compatqual.NullableDecl;
+
+/** @author Colin Decker */
+public final class TestAttributeProvider extends AttributeProvider {
+
+ private static final ImmutableSet<String> ATTRIBUTES = ImmutableSet.of("foo", "bar", "baz");
+
+ @Override
+ public String name() {
+ return "test";
+ }
+
+ @Override
+ public ImmutableSet<String> inherits() {
+ return ImmutableSet.of("basic");
+ }
+
+ @Override
+ public ImmutableSet<String> fixedAttributes() {
+ return ATTRIBUTES;
+ }
+
+ @Override
+ public ImmutableMap<String, ?> defaultValues(Map<String, ?> userDefaults) {
+ Map<String, Object> result = new HashMap<>();
+
+ Long bar = 0L;
+ Integer baz = 1;
+ if (userDefaults.containsKey("test:bar")) {
+ bar = checkType("test", "bar", userDefaults.get("test:bar"), Number.class).longValue();
+ }
+ if (userDefaults.containsKey("test:baz")) {
+ baz = checkType("test", "baz", userDefaults.get("test:baz"), Integer.class);
+ }
+
+ result.put("test:bar", bar);
+ result.put("test:baz", baz);
+ return ImmutableMap.copyOf(result);
+ }
+
+ @Override
+ public void set(File file, String view, String attribute, Object value, boolean create) {
+ switch (attribute) {
+ case "bar":
+ checkNotCreate(view, attribute, create);
+ file.setAttribute(
+ "test", "bar", checkType(view, attribute, value, Number.class).longValue());
+ break;
+ case "baz":
+ file.setAttribute("test", "baz", checkType(view, attribute, value, Integer.class));
+ break;
+ default:
+ throw unsettable(view, attribute, create);
+ }
+ }
+
+ @Override
+ public Object get(File file, String attribute) {
+ if (attribute.equals("foo")) {
+ return "hello";
+ }
+ return file.getAttribute("test", attribute);
+ }
+
+ @Override
+ public Class<TestAttributeView> viewType() {
+ return TestAttributeView.class;
+ }
+
+ @Override
+ public TestAttributeView view(
+ FileLookup lookup, ImmutableMap<String, FileAttributeView> inheritedViews) {
+ return new View(lookup, (BasicFileAttributeView) inheritedViews.get("basic"));
+ }
+
+ @Override
+ public Class<TestAttributes> attributesType() {
+ return TestAttributes.class;
+ }
+
+ @Override
+ public TestAttributes readAttributes(File file) {
+ return new Attributes(file);
+ }
+
+ static final class View implements TestAttributeView {
+
+ private final FileLookup lookup;
+ private final BasicFileAttributeView basicView;
+
+ public View(FileLookup lookup, BasicFileAttributeView basicView) {
+ this.lookup = checkNotNull(lookup);
+ this.basicView = checkNotNull(basicView);
+ }
+
+ @Override
+ public String name() {
+ return "test";
+ }
+
+ @Override
+ public Attributes readAttributes() throws IOException {
+ return new Attributes(lookup.lookup());
+ }
+
+ @Override
+ public void setTimes(
+ @NullableDecl FileTime lastModifiedTime,
+ @NullableDecl FileTime lastAccessTime,
+ @NullableDecl FileTime createTime)
+ throws IOException {
+ basicView.setTimes(lastModifiedTime, lastAccessTime, createTime);
+ }
+
+ @Override
+ public void setBar(long bar) throws IOException {
+ lookup.lookup().setAttribute("test", "bar", bar);
+ }
+
+ @Override
+ public void setBaz(int baz) throws IOException {
+ lookup.lookup().setAttribute("test", "baz", baz);
+ }
+ }
+
+ static final class Attributes implements TestAttributes {
+
+ private final Long bar;
+ private final Integer baz;
+
+ public Attributes(File file) {
+ this.bar = (Long) file.getAttribute("test", "bar");
+ this.baz = (Integer) file.getAttribute("test", "baz");
+ }
+
+ @Override
+ public String foo() {
+ return "hello";
+ }
+
+ @Override
+ public long bar() {
+ return bar;
+ }
+
+ @Override
+ public int baz() {
+ return baz;
+ }
+
+ // BasicFileAttributes is just implemented here because readAttributes requires a subtype of
+ // BasicFileAttributes -- methods are not implemented
+
+ @Override
+ public FileTime lastModifiedTime() {
+ return null;
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ return null;
+ }
+
+ @Override
+ public FileTime creationTime() {
+ return null;
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ return false;
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return false;
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ return false;
+ }
+
+ @Override
+ public boolean isOther() {
+ return false;
+ }
+
+ @Override
+ public long size() {
+ return 0;
+ }
+
+ @Override
+ public Object fileKey() {
+ return null;
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/TestAttributeView.java b/jimfs/src/test/java/com/google/common/jimfs/TestAttributeView.java
new file mode 100644
index 0000000..c9c4cd5
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/TestAttributeView.java
@@ -0,0 +1,30 @@
+/*
+ * 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 java.io.IOException;
+import java.nio.file.attribute.BasicFileAttributeView;
+
+/** @author Colin Decker */
+public interface TestAttributeView extends BasicFileAttributeView {
+
+ TestAttributes readAttributes() throws IOException;
+
+ void setBar(long bar) throws IOException;
+
+ void setBaz(int baz) throws IOException;
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/TestAttributes.java b/jimfs/src/test/java/com/google/common/jimfs/TestAttributes.java
new file mode 100644
index 0000000..fc66f80
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/TestAttributes.java
@@ -0,0 +1,29 @@
+/*
+ * 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 java.nio.file.attribute.BasicFileAttributes;
+
+/** @author Colin Decker */
+public interface TestAttributes extends BasicFileAttributes {
+
+ String foo();
+
+ long bar();
+
+ int baz();
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java b/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java
new file mode 100644
index 0000000..30e0930
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/TestUtils.java
@@ -0,0 +1,144 @@
+/*
+ * 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 java.nio.file.LinkOption.NOFOLLOW_LINKS;
+import static org.junit.Assert.assertFalse;
+
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/** @author Colin Decker */
+public final class TestUtils {
+
+ private TestUtils() {}
+
+ public static byte[] bytes(int... bytes) {
+ byte[] result = new byte[bytes.length];
+ for (int i = 0; i < bytes.length; i++) {
+ result[i] = (byte) bytes[i];
+ }
+ return result;
+ }
+
+ public static byte[] bytes(String bytes) {
+ byte[] result = new byte[bytes.length()];
+ for (int i = 0; i < bytes.length(); i++) {
+ String digit = bytes.substring(i, i + 1);
+ result[i] = Byte.parseByte(digit);
+ }
+ return result;
+ }
+
+ public static byte[] preFilledBytes(int length, int fillValue) {
+ byte[] result = new byte[length];
+ Arrays.fill(result, (byte) fillValue);
+ return result;
+ }
+
+ public static byte[] preFilledBytes(int length) {
+ byte[] bytes = new byte[length];
+ for (int i = 0; i < length; i++) {
+ bytes[i] = (byte) i;
+ }
+ return bytes;
+ }
+
+ public static ByteBuffer buffer(String bytes) {
+ return ByteBuffer.wrap(bytes(bytes));
+ }
+
+ public static Iterable<ByteBuffer> buffers(String... bytes) {
+ List<ByteBuffer> result = new ArrayList<>();
+ for (String b : bytes) {
+ result.add(buffer(b));
+ }
+ return result;
+ }
+
+ /** Returns a number of permutations of the given path that should all locate the same file. */
+ public static Iterable<Path> permutations(Path path) throws IOException {
+ Path workingDir = path.getFileSystem().getPath("").toRealPath();
+ boolean directory = Files.isDirectory(path);
+
+ Set<Path> results = new HashSet<>();
+ results.add(path);
+ if (path.isAbsolute()) {
+ results.add(workingDir.relativize(path));
+ } else {
+ results.add(workingDir.resolve(path));
+ }
+ if (directory) {
+ for (Path p : ImmutableList.copyOf(results)) {
+ results.add(p.resolve("."));
+ results.add(p.resolve(".").resolve("."));
+ Path fileName = p.getFileName();
+ if (fileName != null
+ && !fileName.toString().equals(".")
+ && !fileName.toString().equals("..")) {
+ results.add(p.resolve("..").resolve(fileName));
+ results.add(p.resolve("..").resolve(".").resolve(fileName));
+ results.add(p.resolve("..").resolve(".").resolve(fileName).resolve("."));
+ results.add(p.resolve(".").resolve("..").resolve(".").resolve(fileName));
+ }
+ }
+
+ try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
+ for (Path child : stream) {
+ if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
+ Path childName = child.getFileName();
+ for (Path p : ImmutableList.copyOf(results)) {
+ results.add(p.resolve(childName).resolve(".."));
+ results.add(p.resolve(childName).resolve(".").resolve(".").resolve(".."));
+ results.add(p.resolve(childName).resolve("..").resolve("."));
+ results.add(
+ p.resolve(childName).resolve("..").resolve(childName).resolve(".").resolve(".."));
+ }
+ break; // no need to add more than one child
+ }
+ }
+ }
+ }
+ return results;
+ }
+
+ // equivalent to the Junit 4.11 method.
+ public static void assertNotEquals(Object unexpected, Object actual) {
+ assertFalse(
+ "Values should be different. Actual: " + actual, Objects.equals(unexpected, actual));
+ }
+
+ static RegularFile regularFile(int size) {
+ RegularFile file = RegularFile.create(0, new HeapDisk(8096, 1000, 1000));
+ try {
+ file.write(0, new byte[size], 0, size);
+ return file;
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java
new file mode 100644
index 0000000..dc32d20
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/UnixAttributeProviderTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.UserLookupService.createGroupPrincipal;
+import static com.google.common.jimfs.UserLookupService.createUserPrincipal;
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link UnixAttributeProvider}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+@SuppressWarnings("OctalInteger")
+public class UnixAttributeProviderTest
+ extends AbstractAttributeProviderTest<UnixAttributeProvider> {
+
+ @Override
+ protected UnixAttributeProvider createProvider() {
+ return new UnixAttributeProvider();
+ }
+
+ @Override
+ protected Set<? extends AttributeProvider> createInheritedProviders() {
+ return ImmutableSet.of(
+ new BasicAttributeProvider(), new OwnerAttributeProvider(), new PosixAttributeProvider());
+ }
+
+ @Test
+ public void testInitialAttributes() {
+ // unix provider relies on other providers to set their initial attributes
+ file.setAttribute("owner", "owner", createUserPrincipal("foo"));
+ file.setAttribute("posix", "group", createGroupPrincipal("bar"));
+ file.setAttribute(
+ "posix", "permissions", ImmutableSet.copyOf(PosixFilePermissions.fromString("rw-r--r--")));
+
+ // these are pretty much meaningless here since they aren't properties this
+ // file system actually has, so don't really care about the exact value of these
+ assertThat(provider.get(file, "uid")).isInstanceOf(Integer.class);
+ assertThat(provider.get(file, "gid")).isInstanceOf(Integer.class);
+ assertThat(provider.get(file, "rdev")).isEqualTo(0L);
+ assertThat(provider.get(file, "dev")).isEqualTo(1L);
+ assertThat(provider.get(file, "ino")).isInstanceOf(Integer.class);
+
+ // these have logical origins in attributes from other views
+ assertThat(provider.get(file, "mode")).isEqualTo(0644); // rw-r--r--
+ assertThat(provider.get(file, "ctime")).isEqualTo(FileTime.fromMillis(file.getCreationTime()));
+
+ // this is based on a property this file system does actually have
+ assertThat(provider.get(file, "nlink")).isEqualTo(1);
+
+ file.incrementLinkCount();
+ assertThat(provider.get(file, "nlink")).isEqualTo(2);
+ file.decrementLinkCount();
+ assertThat(provider.get(file, "nlink")).isEqualTo(1);
+ }
+
+ @Test
+ public void testSet() {
+ assertSetFails("unix:uid", 1);
+ assertSetFails("unix:gid", 1);
+ assertSetFails("unix:rdev", 1L);
+ assertSetFails("unix:dev", 1L);
+ assertSetFails("unix:ino", 1);
+ assertSetFails("unix:mode", 1);
+ assertSetFails("unix:ctime", 1L);
+ assertSetFails("unix:nlink", 1);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/UnixPathTypeTest.java b/jimfs/src/test/java/com/google/common/jimfs/UnixPathTypeTest.java
new file mode 100644
index 0000000..5bc6cb5
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/UnixPathTypeTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.PathTypeTest.assertParseResult;
+import static com.google.common.jimfs.PathTypeTest.assertUriRoundTripsCorrectly;
+import static com.google.common.jimfs.PathTypeTest.fileSystemUri;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import java.net.URI;
+import java.nio.file.InvalidPathException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link UnixPathType}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class UnixPathTypeTest {
+
+ @Test
+ public void testUnix() {
+ PathType unix = PathType.unix();
+ assertThat(unix.getSeparator()).isEqualTo("/");
+ assertThat(unix.getOtherSeparators()).isEqualTo("");
+
+ // "//foo/bar" is what will be passed to parsePath if "/", "foo", "bar" is passed to getPath
+ PathType.ParseResult path = unix.parsePath("//foo/bar");
+ assertParseResult(path, "/", "foo", "bar");
+ assertThat(unix.toString(path.root(), path.names())).isEqualTo("/foo/bar");
+
+ PathType.ParseResult path2 = unix.parsePath("foo/bar/");
+ assertParseResult(path2, null, "foo", "bar");
+ assertThat(unix.toString(path2.root(), path2.names())).isEqualTo("foo/bar");
+ }
+
+ @Test
+ public void testUnix_toUri() {
+ URI fileUri = PathType.unix().toUri(fileSystemUri, "/", ImmutableList.of("foo", "bar"), false);
+ assertThat(fileUri.toString()).isEqualTo("jimfs://foo/foo/bar");
+ assertThat(fileUri.getPath()).isEqualTo("/foo/bar");
+
+ URI directoryUri =
+ PathType.unix().toUri(fileSystemUri, "/", ImmutableList.of("foo", "bar"), true);
+ assertThat(directoryUri.toString()).isEqualTo("jimfs://foo/foo/bar/");
+ assertThat(directoryUri.getPath()).isEqualTo("/foo/bar/");
+
+ URI rootUri = PathType.unix().toUri(fileSystemUri, "/", ImmutableList.<String>of(), true);
+ assertThat(rootUri.toString()).isEqualTo("jimfs://foo/");
+ assertThat(rootUri.getPath()).isEqualTo("/");
+ }
+
+ @Test
+ public void testUnix_toUri_escaping() {
+ URI uri = PathType.unix().toUri(fileSystemUri, "/", ImmutableList.of("foo bar"), false);
+ assertThat(uri.toString()).isEqualTo("jimfs://foo/foo%20bar");
+ assertThat(uri.getRawPath()).isEqualTo("/foo%20bar");
+ assertThat(uri.getPath()).isEqualTo("/foo bar");
+ }
+
+ @Test
+ public void testUnix_uriRoundTrips() {
+ assertUriRoundTripsCorrectly(PathType.unix(), "/");
+ assertUriRoundTripsCorrectly(PathType.unix(), "/foo");
+ assertUriRoundTripsCorrectly(PathType.unix(), "/foo/bar/baz");
+ assertUriRoundTripsCorrectly(PathType.unix(), "/foo/bar baz/one/two");
+ assertUriRoundTripsCorrectly(PathType.unix(), "/foo bar");
+ assertUriRoundTripsCorrectly(PathType.unix(), "/foo bar/");
+ assertUriRoundTripsCorrectly(PathType.unix(), "/foo bar/baz/one");
+ }
+
+ @Test
+ public void testUnix_illegalCharacters() {
+ try {
+ PathType.unix().parsePath("/foo/bar\0");
+ fail();
+ } catch (InvalidPathException expected) {
+ assertEquals(8, expected.getIndex());
+ }
+
+ try {
+ PathType.unix().parsePath("/\u00001/foo");
+ fail();
+ } catch (InvalidPathException expected) {
+ assertEquals(1, expected.getIndex());
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/UrlTest.java b/jimfs/src/test/java/com/google/common/jimfs/UrlTest.java
new file mode 100644
index 0000000..f724c7f
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/UrlTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2015 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.base.StandardSystemProperty.LINE_SEPARATOR;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Range;
+import com.google.common.io.Resources;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests that {@link URL} instances can be created and used from jimfs URIs.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class UrlTest {
+
+ private final FileSystem fs = Jimfs.newFileSystem(Configuration.unix());
+ private Path path = fs.getPath("foo");
+
+ @Test
+ public void creatUrl() throws MalformedURLException {
+ URL url = path.toUri().toURL();
+ assertThat(url).isNotNull();
+ }
+
+ @Test
+ public void readFromUrl() throws IOException {
+ Files.write(path, ImmutableList.of("Hello World"), UTF_8);
+
+ URL url = path.toUri().toURL();
+ assertThat(Resources.asCharSource(url, UTF_8).read())
+ .isEqualTo("Hello World" + LINE_SEPARATOR.value());
+ }
+
+ @Test
+ public void readDirectoryContents() throws IOException {
+ Files.createDirectory(path);
+ Files.createFile(path.resolve("a.txt"));
+ Files.createFile(path.resolve("b.txt"));
+ Files.createDirectory(path.resolve("c"));
+
+ URL url = path.toUri().toURL();
+ assertThat(Resources.asCharSource(url, UTF_8).read()).isEqualTo("a.txt\nb.txt\nc\n");
+ }
+
+ @Test
+ public void headers() throws IOException {
+ byte[] bytes = {1, 2, 3};
+ Files.write(path, bytes);
+ FileTime lastModified = Files.getLastModifiedTime(path);
+
+ URL url = path.toUri().toURL();
+ URLConnection conn = url.openConnection();
+
+ // read header fields directly
+ assertThat(conn.getHeaderFields()).containsEntry("content-length", ImmutableList.of("3"));
+ assertThat(conn.getHeaderFields())
+ .containsEntry("content-type", ImmutableList.of("application/octet-stream"));
+
+ if (lastModified != null) {
+ assertThat(conn.getHeaderFields()).containsKey("last-modified");
+ assertThat(conn.getHeaderFields()).hasSize(3);
+ } else {
+ assertThat(conn.getHeaderFields()).hasSize(2);
+ }
+
+ // use the specific methods for reading the expected headers
+ assertThat(conn.getContentLengthLong()).isEqualTo(Files.size(path));
+ assertThat(conn.getContentType()).isEqualTo("application/octet-stream");
+
+ if (lastModified != null) {
+ // The HTTP date format does not include milliseconds, which means that the last modified time
+ // returned from the connection may not be exactly the same as that of the file system itself.
+ // The difference should less than 1000ms though, and should never be greater.
+ long difference = lastModified.toMillis() - conn.getLastModified();
+ assertThat(difference).isIn(Range.closedOpen(0L, 1000L));
+ } else {
+ assertThat(conn.getLastModified()).isEqualTo(0L);
+ }
+ }
+
+ @Test
+ public void contentType() throws IOException {
+ path = fs.getPath("foo.txt");
+ Files.write(path, ImmutableList.of("Hello World"), UTF_8);
+
+ URL url = path.toUri().toURL();
+ URLConnection conn = url.openConnection();
+
+ // Should be text/plain, but this is entirely dependent on the installed FileTypeDetectors
+ String detectedContentType = Files.probeContentType(path);
+ if (detectedContentType == null) {
+ assertThat(conn.getContentType()).isEqualTo("application/octet-stream");
+ } else {
+ assertThat(conn.getContentType()).isEqualTo(detectedContentType);
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/UserDefinedAttributeProviderTest.java b/jimfs/src/test/java/com/google/common/jimfs/UserDefinedAttributeProviderTest.java
new file mode 100644
index 0000000..67a95a8
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/UserDefinedAttributeProviderTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.util.Arrays;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link UserDefinedAttributeProvider}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class UserDefinedAttributeProviderTest
+ extends AbstractAttributeProviderTest<UserDefinedAttributeProvider> {
+
+ @Override
+ protected UserDefinedAttributeProvider createProvider() {
+ return new UserDefinedAttributeProvider();
+ }
+
+ @Override
+ protected Set<? extends AttributeProvider> createInheritedProviders() {
+ return ImmutableSet.of();
+ }
+
+ @Test
+ public void testInitialAttributes() {
+ // no initial attributes
+ assertThat(ImmutableList.copyOf(file.getAttributeKeys())).isEmpty();
+ assertThat(provider.attributes(file)).isEmpty();
+ }
+
+ @Test
+ public void testGettingAndSetting() {
+ byte[] bytes = {0, 1, 2, 3};
+ provider.set(file, "user", "one", bytes, false);
+ provider.set(file, "user", "two", ByteBuffer.wrap(bytes), false);
+
+ byte[] one = (byte[]) provider.get(file, "one");
+ byte[] two = (byte[]) provider.get(file, "two");
+ assertThat(Arrays.equals(one, bytes)).isTrue();
+ assertThat(Arrays.equals(two, bytes)).isTrue();
+
+ assertSetFails("foo", "hello");
+
+ assertThat(provider.attributes(file)).containsExactly("one", "two");
+ }
+
+ @Test
+ public void testSetOnCreate() {
+ assertSetFailsOnCreate("anything", new byte[0]);
+ }
+
+ @Test
+ public void testView() throws IOException {
+ UserDefinedFileAttributeView view = provider.view(fileLookup(), NO_INHERITED_VIEWS);
+ assertNotNull(view);
+
+ assertThat(view.name()).isEqualTo("user");
+ assertThat(view.list()).isEmpty();
+
+ byte[] b1 = {0, 1, 2};
+ byte[] b2 = {0, 1, 2, 3, 4};
+
+ view.write("b1", ByteBuffer.wrap(b1));
+ view.write("b2", ByteBuffer.wrap(b2));
+
+ assertThat(view.list()).containsAtLeast("b1", "b2");
+ assertThat(file.getAttributeKeys()).containsExactly("user:b1", "user:b2");
+
+ assertThat(view.size("b1")).isEqualTo(3);
+ assertThat(view.size("b2")).isEqualTo(5);
+
+ ByteBuffer buf1 = ByteBuffer.allocate(view.size("b1"));
+ ByteBuffer buf2 = ByteBuffer.allocate(view.size("b2"));
+
+ view.read("b1", buf1);
+ view.read("b2", buf2);
+
+ assertThat(Arrays.equals(b1, buf1.array())).isTrue();
+ assertThat(Arrays.equals(b2, buf2.array())).isTrue();
+
+ view.delete("b2");
+
+ assertThat(view.list()).containsExactly("b1");
+ assertThat(file.getAttributeKeys()).containsExactly("user:b1");
+
+ try {
+ view.size("b2");
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("not set");
+ }
+
+ try {
+ view.read("b2", ByteBuffer.allocate(10));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected.getMessage()).contains("not set");
+ }
+
+ view.write("b1", ByteBuffer.wrap(b2));
+ assertThat(view.size("b1")).isEqualTo(5);
+
+ view.delete("b2"); // succeeds
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/UserLookupServiceTest.java b/jimfs/src/test/java/com/google/common/jimfs/UserLookupServiceTest.java
new file mode 100644
index 0000000..04594ca
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/UserLookupServiceTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.nio.file.attribute.GroupPrincipal;
+import java.nio.file.attribute.UserPrincipal;
+import java.nio.file.attribute.UserPrincipalLookupService;
+import java.nio.file.attribute.UserPrincipalNotFoundException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link UserLookupService}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class UserLookupServiceTest {
+
+ @Test
+ public void testUserLookupService() throws IOException {
+ UserPrincipalLookupService service = new UserLookupService(true);
+ UserPrincipal bob1 = service.lookupPrincipalByName("bob");
+ UserPrincipal bob2 = service.lookupPrincipalByName("bob");
+ UserPrincipal alice = service.lookupPrincipalByName("alice");
+
+ assertThat(bob1).isEqualTo(bob2);
+ assertThat(bob1).isNotEqualTo(alice);
+
+ GroupPrincipal group1 = service.lookupPrincipalByGroupName("group");
+ GroupPrincipal group2 = service.lookupPrincipalByGroupName("group");
+ GroupPrincipal foo = service.lookupPrincipalByGroupName("foo");
+
+ assertThat(group1).isEqualTo(group2);
+ assertThat(group1).isNotEqualTo(foo);
+ }
+
+ @Test
+ public void testServiceNotSupportingGroups() throws IOException {
+ UserPrincipalLookupService service = new UserLookupService(false);
+
+ try {
+ service.lookupPrincipalByGroupName("group");
+ fail();
+ } catch (UserPrincipalNotFoundException expected) {
+ assertThat(expected.getName()).isEqualTo("group");
+ }
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/WatchServiceConfigurationTest.java b/jimfs/src/test/java/com/google/common/jimfs/WatchServiceConfigurationTest.java
new file mode 100644
index 0000000..7a98a7d
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/WatchServiceConfigurationTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2016 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.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.io.IOException;
+import java.nio.file.WatchService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link WatchServiceConfiguration}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class WatchServiceConfigurationTest {
+
+ private JimfsFileSystem fs;
+
+ @Before
+ public void setUp() {
+ // kind of putting the cart before the horse maybe, but it's the easiest way to get valid
+ // instances of both a FileSystemView and a PathService
+ fs = (JimfsFileSystem) Jimfs.newFileSystem();
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ fs.close();
+ fs = null;
+ }
+
+ @Test
+ public void testPollingConfig() {
+ WatchServiceConfiguration polling = WatchServiceConfiguration.polling(50, MILLISECONDS);
+ WatchService watchService = polling.newWatchService(fs.getDefaultView(), fs.getPathService());
+ assertThat(watchService).isInstanceOf(PollingWatchService.class);
+
+ PollingWatchService pollingWatchService = (PollingWatchService) watchService;
+ assertThat(pollingWatchService.interval).isEqualTo(50);
+ assertThat(pollingWatchService.timeUnit).isEqualTo(MILLISECONDS);
+ }
+
+ @Test
+ public void testDefaultConfig() {
+ WatchService watchService =
+ WatchServiceConfiguration.DEFAULT.newWatchService(fs.getDefaultView(), fs.getPathService());
+ assertThat(watchService).isInstanceOf(PollingWatchService.class);
+
+ PollingWatchService pollingWatchService = (PollingWatchService) watchService;
+ assertThat(pollingWatchService.interval).isEqualTo(5);
+ assertThat(pollingWatchService.timeUnit).isEqualTo(SECONDS);
+ }
+}
diff --git a/jimfs/src/test/java/com/google/common/jimfs/WindowsPathTypeTest.java b/jimfs/src/test/java/com/google/common/jimfs/WindowsPathTypeTest.java
new file mode 100644
index 0000000..2da1280
--- /dev/null
+++ b/jimfs/src/test/java/com/google/common/jimfs/WindowsPathTypeTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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.PathType.windows;
+import static com.google.common.jimfs.PathTypeTest.assertParseResult;
+import static com.google.common.jimfs.PathTypeTest.assertUriRoundTripsCorrectly;
+import static com.google.common.jimfs.PathTypeTest.fileSystemUri;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import java.net.URI;
+import java.nio.file.InvalidPathException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for {@link WindowsPathType}.
+ *
+ * @author Colin Decker
+ */
+@RunWith(JUnit4.class)
+public class WindowsPathTypeTest {
+
+ @Test
+ public void testWindows() {
+ PathType windows = PathType.windows();
+ assertThat(windows.getSeparator()).isEqualTo("\\");
+ assertThat(windows.getOtherSeparators()).isEqualTo("/");
+
+ // "C:\\foo\bar" results from "C:\", "foo", "bar" passed to getPath
+ PathType.ParseResult path = windows.parsePath("C:\\\\foo\\bar");
+ assertParseResult(path, "C:\\", "foo", "bar");
+ assertThat(windows.toString(path.root(), path.names())).isEqualTo("C:\\foo\\bar");
+
+ PathType.ParseResult path2 = windows.parsePath("foo/bar/");
+ assertParseResult(path2, null, "foo", "bar");
+ assertThat(windows.toString(path2.root(), path2.names())).isEqualTo("foo\\bar");
+
+ PathType.ParseResult path3 = windows.parsePath("hello world/foo/bar");
+ assertParseResult(path3, null, "hello world", "foo", "bar");
+ assertThat(windows.toString(null, path3.names())).isEqualTo("hello world\\foo\\bar");
+ }
+
+ @Test
+ public void testWindows_relativePathsWithDriveRoot_unsupported() {
+ try {
+ windows().parsePath("C:");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+
+ try {
+ windows().parsePath("C:foo\\bar");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+ }
+
+ @Test
+ public void testWindows_absolutePathOnCurrentDrive_unsupported() {
+ try {
+ windows().parsePath("\\foo\\bar");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+
+ try {
+ windows().parsePath("\\");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+ }
+
+ @Test
+ public void testWindows_uncPaths() {
+ PathType windows = PathType.windows();
+ PathType.ParseResult path = windows.parsePath("\\\\host\\share");
+ assertParseResult(path, "\\\\host\\share\\");
+
+ path = windows.parsePath("\\\\HOST\\share\\foo\\bar");
+ assertParseResult(path, "\\\\HOST\\share\\", "foo", "bar");
+
+ try {
+ windows.parsePath("\\\\");
+ fail();
+ } catch (InvalidPathException expected) {
+ assertThat(expected.getInput()).isEqualTo("\\\\");
+ assertThat(expected.getReason()).isEqualTo("UNC path is missing hostname");
+ }
+
+ try {
+ windows.parsePath("\\\\host");
+ fail();
+ } catch (InvalidPathException expected) {
+ assertThat(expected.getInput()).isEqualTo("\\\\host");
+ assertThat(expected.getReason()).isEqualTo("UNC path is missing sharename");
+ }
+
+ try {
+ windows.parsePath("\\\\host\\");
+ fail();
+ } catch (InvalidPathException expected) {
+ assertThat(expected.getInput()).isEqualTo("\\\\host\\");
+ assertThat(expected.getReason()).isEqualTo("UNC path is missing sharename");
+ }
+
+ try {
+ windows.parsePath("//host");
+ fail();
+ } catch (InvalidPathException expected) {
+ assertThat(expected.getInput()).isEqualTo("//host");
+ assertThat(expected.getReason()).isEqualTo("UNC path is missing sharename");
+ }
+ }
+
+ @Test
+ public void testWindows_illegalNames() {
+ try {
+ windows().parsePath("foo<bar");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+
+ try {
+ windows().parsePath("foo?");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+
+ try {
+ windows().parsePath("foo ");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+
+ try {
+ windows().parsePath("foo \\bar");
+ fail();
+ } catch (InvalidPathException expected) {
+ }
+ }
+
+ @Test
+ public void testWindows_toUri_normal() {
+ URI fileUri =
+ PathType.windows().toUri(fileSystemUri, "C:\\", ImmutableList.of("foo", "bar"), false);
+ assertThat(fileUri.toString()).isEqualTo("jimfs://foo/C:/foo/bar");
+ assertThat(fileUri.getPath()).isEqualTo("/C:/foo/bar");
+
+ URI directoryUri =
+ PathType.windows().toUri(fileSystemUri, "C:\\", ImmutableList.of("foo", "bar"), true);
+ assertThat(directoryUri.toString()).isEqualTo("jimfs://foo/C:/foo/bar/");
+ assertThat(directoryUri.getPath()).isEqualTo("/C:/foo/bar/");
+
+ URI rootUri = PathType.windows().toUri(fileSystemUri, "C:\\", ImmutableList.<String>of(), true);
+ assertThat(rootUri.toString()).isEqualTo("jimfs://foo/C:/");
+ assertThat(rootUri.getPath()).isEqualTo("/C:/");
+ }
+
+ @Test
+ public void testWindows_toUri_unc() {
+ URI fileUri =
+ PathType.windows()
+ .toUri(fileSystemUri, "\\\\host\\share\\", ImmutableList.of("foo", "bar"), false);
+ assertThat(fileUri.toString()).isEqualTo("jimfs://foo//host/share/foo/bar");
+ assertThat(fileUri.getPath()).isEqualTo("//host/share/foo/bar");
+
+ URI rootUri =
+ PathType.windows()
+ .toUri(fileSystemUri, "\\\\host\\share\\", ImmutableList.<String>of(), true);
+ assertThat(rootUri.toString()).isEqualTo("jimfs://foo//host/share/");
+ assertThat(rootUri.getPath()).isEqualTo("//host/share/");
+ }
+
+ @Test
+ public void testWindows_toUri_escaping() {
+ URI uri =
+ PathType.windows()
+ .toUri(fileSystemUri, "C:\\", ImmutableList.of("Users", "foo", "My Documents"), true);
+ assertThat(uri.toString()).isEqualTo("jimfs://foo/C:/Users/foo/My%20Documents/");
+ assertThat(uri.getRawPath()).isEqualTo("/C:/Users/foo/My%20Documents/");
+ assertThat(uri.getPath()).isEqualTo("/C:/Users/foo/My Documents/");
+ }
+
+ @Test
+ public void testWindows_uriRoundTrips_normal() {
+ assertUriRoundTripsCorrectly(PathType.windows(), "C:\\");
+ assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo");
+ assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo\\bar\\baz");
+ assertUriRoundTripsCorrectly(PathType.windows(), "C:\\Users\\foo\\My Documents\\");
+ assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo bar");
+ assertUriRoundTripsCorrectly(PathType.windows(), "C:\\foo bar\\baz");
+ }
+
+ @Test
+ public void testWindows_uriRoundTrips_unc() {
+ assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share");
+ assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\");
+ assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo");
+ assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo\\bar\\baz");
+ assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\Users\\foo\\My Documents\\");
+ assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo bar");
+ assertUriRoundTripsCorrectly(PathType.windows(), "\\\\host\\share\\foo bar\\baz");
+ }
+}