blob: 0cac0ad744548226715583f81cda895cd3a6bcd8 [file] [log] [blame]
/*
* 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 org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
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 javax.annotation.Nullable;
/**
* 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(@Nullable 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;
}
}