diff options
Diffstat (limited to 'jimfs/src/test/java')
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"); + } +} |