• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2013 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.common.jimfs;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.jimfs.TestUtils.regularFile;
21 import static com.google.common.truth.Truth.assertThat;
22 import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
23 import static org.junit.Assert.fail;
24 
25 import com.google.common.base.Joiner;
26 import com.google.common.base.Splitter;
27 import com.google.common.base.Strings;
28 import java.io.IOException;
29 import java.nio.file.LinkOption;
30 import java.nio.file.NoSuchFileException;
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.Random;
34 import org.checkerframework.checker.nullness.compatqual.NullableDecl;
35 import org.junit.Before;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.junit.runners.JUnit4;
39 
40 /**
41  * Tests for {@link FileTree}.
42  *
43  * @author Colin Decker
44  */
45 @RunWith(JUnit4.class)
46 public class FileTreeTest {
47 
48   /*
49    * Directory structure. Each file should have a unique name.
50    *
51    * /
52    *   work/
53    *     one/
54    *       two/
55    *         three/
56    *       eleven
57    *     four/
58    *       five -> /foo
59    *       six -> ../one
60    *       loop -> ../four/loop
61    *   foo/
62    *     bar/
63    * $
64    *   a/
65    *     b/
66    *       c/
67    */
68 
69   /**
70    * This path service is for unix-like paths, with the exception that it recognizes $ and ! as
71    * roots in addition to /, allowing for up to three roots. When creating a {@linkplain
72    * PathType#toUriPath URI path}, we prefix the path with / to differentiate between a path like
73    * "$foo/bar" and one like "/$foo/bar". They would become "/$foo/bar" and "//$foo/bar"
74    * respectively.
75    */
76   private final PathService pathService =
77       PathServiceTest.fakePathService(
78           new PathType(true, '/') {
79             @Override
80             public ParseResult parsePath(String path) {
81               String root = null;
82               if (path.matches("^[/$!].*")) {
83                 root = path.substring(0, 1);
84                 path = path.substring(1);
85               }
86               return new ParseResult(root, Splitter.on('/').omitEmptyStrings().split(path));
87             }
88 
89             @Override
90             public String toString(@NullableDecl String root, Iterable<String> names) {
91               root = Strings.nullToEmpty(root);
92               return root + Joiner.on('/').join(names);
93             }
94 
95             @Override
96             public String toUriPath(String root, Iterable<String> names, boolean directory) {
97               // need to add extra / to differentiate between paths "/$foo/bar" and "$foo/bar".
98               return "/" + toString(root, names);
99             }
100 
101             @Override
102             public ParseResult parseUriPath(String uriPath) {
103               checkArgument(
104                   uriPath.matches("^/[/$!].*"), "uriPath (%s) must start with // or /$ or /!");
105               return parsePath(uriPath.substring(1)); // skip leading /
106             }
107           },
108           false);
109 
110   private FileTree fileTree;
111   private File workingDirectory;
112   private final Map<String, File> files = new HashMap<>();
113 
114   @Before
setUp()115   public void setUp() {
116     Directory root = Directory.createRoot(0, Name.simple("/"));
117     files.put("/", root);
118 
119     Directory otherRoot = Directory.createRoot(2, Name.simple("$"));
120     files.put("$", otherRoot);
121 
122     Map<Name, Directory> roots = new HashMap<>();
123     roots.put(Name.simple("/"), root);
124     roots.put(Name.simple("$"), otherRoot);
125 
126     fileTree = new FileTree(roots);
127 
128     workingDirectory = createDirectory("/", "work");
129 
130     createDirectory("work", "one");
131     createDirectory("one", "two");
132     createFile("one", "eleven");
133     createDirectory("two", "three");
134     createDirectory("work", "four");
135     createSymbolicLink("four", "five", "/foo");
136     createSymbolicLink("four", "six", "../one");
137     createSymbolicLink("four", "loop", "../four/loop");
138     createDirectory("/", "foo");
139     createDirectory("foo", "bar");
140     createDirectory("$", "a");
141     createDirectory("a", "b");
142     createDirectory("b", "c");
143   }
144 
145   // absolute lookups
146 
147   @Test
testLookup_root()148   public void testLookup_root() throws IOException {
149     assertExists(lookup("/"), "/", "/");
150     assertExists(lookup("$"), "$", "$");
151   }
152 
153   @Test
testLookup_nonExistentRoot()154   public void testLookup_nonExistentRoot() throws IOException {
155     try {
156       lookup("!");
157       fail();
158     } catch (NoSuchFileException expected) {
159     }
160 
161     try {
162       lookup("!a");
163       fail();
164     } catch (NoSuchFileException expected) {
165     }
166   }
167 
168   @Test
testLookup_absolute()169   public void testLookup_absolute() throws IOException {
170     assertExists(lookup("/work"), "/", "work");
171     assertExists(lookup("/work/one/two/three"), "two", "three");
172     assertExists(lookup("$a"), "$", "a");
173     assertExists(lookup("$a/b/c"), "b", "c");
174   }
175 
176   @Test
testLookup_absolute_notExists()177   public void testLookup_absolute_notExists() throws IOException {
178     try {
179       lookup("/a/b");
180       fail();
181     } catch (NoSuchFileException expected) {
182     }
183 
184     try {
185       lookup("/work/one/foo/bar");
186       fail();
187     } catch (NoSuchFileException expected) {
188     }
189 
190     try {
191       lookup("$c/d");
192       fail();
193     } catch (NoSuchFileException expected) {
194     }
195 
196     try {
197       lookup("$a/b/c/d/e");
198       fail();
199     } catch (NoSuchFileException expected) {
200     }
201   }
202 
203   @Test
testLookup_absolute_parentExists()204   public void testLookup_absolute_parentExists() throws IOException {
205     assertParentExists(lookup("/a"), "/");
206     assertParentExists(lookup("/foo/baz"), "foo");
207     assertParentExists(lookup("$c"), "$");
208     assertParentExists(lookup("$a/b/c/d"), "c");
209   }
210 
211   @Test
testLookup_absolute_nonDirectoryIntermediateFile()212   public void testLookup_absolute_nonDirectoryIntermediateFile() throws IOException {
213     try {
214       lookup("/work/one/eleven/twelve");
215       fail();
216     } catch (NoSuchFileException expected) {
217     }
218 
219     try {
220       lookup("/work/one/eleven/twelve/thirteen/fourteen");
221       fail();
222     } catch (NoSuchFileException expected) {
223     }
224   }
225 
226   @Test
testLookup_absolute_intermediateSymlink()227   public void testLookup_absolute_intermediateSymlink() throws IOException {
228     assertExists(lookup("/work/four/five/bar"), "foo", "bar");
229     assertExists(lookup("/work/four/six/two/three"), "two", "three");
230 
231     // NOFOLLOW_LINKS doesn't affect intermediate symlinks
232     assertExists(lookup("/work/four/five/bar", NOFOLLOW_LINKS), "foo", "bar");
233     assertExists(lookup("/work/four/six/two/three", NOFOLLOW_LINKS), "two", "three");
234   }
235 
236   @Test
testLookup_absolute_intermediateSymlink_parentExists()237   public void testLookup_absolute_intermediateSymlink_parentExists() throws IOException {
238     assertParentExists(lookup("/work/four/five/baz"), "foo");
239     assertParentExists(lookup("/work/four/six/baz"), "one");
240   }
241 
242   @Test
testLookup_absolute_finalSymlink()243   public void testLookup_absolute_finalSymlink() throws IOException {
244     assertExists(lookup("/work/four/five"), "/", "foo");
245     assertExists(lookup("/work/four/six"), "work", "one");
246   }
247 
248   @Test
testLookup_absolute_finalSymlink_nofollowLinks()249   public void testLookup_absolute_finalSymlink_nofollowLinks() throws IOException {
250     assertExists(lookup("/work/four/five", NOFOLLOW_LINKS), "four", "five");
251     assertExists(lookup("/work/four/six", NOFOLLOW_LINKS), "four", "six");
252     assertExists(lookup("/work/four/loop", NOFOLLOW_LINKS), "four", "loop");
253   }
254 
255   @Test
testLookup_absolute_symlinkLoop()256   public void testLookup_absolute_symlinkLoop() {
257     try {
258       lookup("/work/four/loop");
259       fail();
260     } catch (IOException expected) {
261     }
262 
263     try {
264       lookup("/work/four/loop/whatever");
265       fail();
266     } catch (IOException expected) {
267     }
268   }
269 
270   @Test
testLookup_absolute_withDotsInPath()271   public void testLookup_absolute_withDotsInPath() throws IOException {
272     assertExists(lookup("/."), "/", "/");
273     assertExists(lookup("/./././."), "/", "/");
274     assertExists(lookup("/work/./one/./././two/three"), "two", "three");
275     assertExists(lookup("/work/./one/./././two/././three"), "two", "three");
276     assertExists(lookup("/work/./one/./././two/three/././."), "two", "three");
277   }
278 
279   @Test
testLookup_absolute_withDotDotsInPath()280   public void testLookup_absolute_withDotDotsInPath() throws IOException {
281     assertExists(lookup("/.."), "/", "/");
282     assertExists(lookup("/../../.."), "/", "/");
283     assertExists(lookup("/work/.."), "/", "/");
284     assertExists(lookup("/work/../work/one/two/../two/three"), "two", "three");
285     assertExists(lookup("/work/one/two/../../four/../one/two/three/../three"), "two", "three");
286     assertExists(lookup("/work/one/two/three/../../two/three/.."), "one", "two");
287     assertExists(lookup("/work/one/two/three/../../two/three/../.."), "work", "one");
288   }
289 
290   @Test
testLookup_absolute_withDotDotsInPath_afterSymlink()291   public void testLookup_absolute_withDotDotsInPath_afterSymlink() throws IOException {
292     assertExists(lookup("/work/four/five/.."), "/", "/");
293     assertExists(lookup("/work/four/six/.."), "/", "work");
294   }
295 
296   // relative lookups
297 
298   @Test
testLookup_relative()299   public void testLookup_relative() throws IOException {
300     assertExists(lookup("one"), "work", "one");
301     assertExists(lookup("one/two/three"), "two", "three");
302   }
303 
304   @Test
testLookup_relative_notExists()305   public void testLookup_relative_notExists() throws IOException {
306     try {
307       lookup("a/b");
308       fail();
309     } catch (NoSuchFileException expected) {
310     }
311 
312     try {
313       lookup("one/foo/bar");
314       fail();
315     } catch (NoSuchFileException expected) {
316     }
317   }
318 
319   @Test
testLookup_relative_parentExists()320   public void testLookup_relative_parentExists() throws IOException {
321     assertParentExists(lookup("a"), "work");
322     assertParentExists(lookup("one/two/four"), "two");
323   }
324 
325   @Test
testLookup_relative_nonDirectoryIntermediateFile()326   public void testLookup_relative_nonDirectoryIntermediateFile() throws IOException {
327     try {
328       lookup("one/eleven/twelve");
329       fail();
330     } catch (NoSuchFileException expected) {
331     }
332 
333     try {
334       lookup("one/eleven/twelve/thirteen/fourteen");
335       fail();
336     } catch (NoSuchFileException expected) {
337     }
338   }
339 
340   @Test
testLookup_relative_intermediateSymlink()341   public void testLookup_relative_intermediateSymlink() throws IOException {
342     assertExists(lookup("four/five/bar"), "foo", "bar");
343     assertExists(lookup("four/six/two/three"), "two", "three");
344 
345     // NOFOLLOW_LINKS doesn't affect intermediate symlinks
346     assertExists(lookup("four/five/bar", NOFOLLOW_LINKS), "foo", "bar");
347     assertExists(lookup("four/six/two/three", NOFOLLOW_LINKS), "two", "three");
348   }
349 
350   @Test
testLookup_relative_intermediateSymlink_parentExists()351   public void testLookup_relative_intermediateSymlink_parentExists() throws IOException {
352     assertParentExists(lookup("four/five/baz"), "foo");
353     assertParentExists(lookup("four/six/baz"), "one");
354   }
355 
356   @Test
testLookup_relative_finalSymlink()357   public void testLookup_relative_finalSymlink() throws IOException {
358     assertExists(lookup("four/five"), "/", "foo");
359     assertExists(lookup("four/six"), "work", "one");
360   }
361 
362   @Test
testLookup_relative_finalSymlink_nofollowLinks()363   public void testLookup_relative_finalSymlink_nofollowLinks() throws IOException {
364     assertExists(lookup("four/five", NOFOLLOW_LINKS), "four", "five");
365     assertExists(lookup("four/six", NOFOLLOW_LINKS), "four", "six");
366     assertExists(lookup("four/loop", NOFOLLOW_LINKS), "four", "loop");
367   }
368 
369   @Test
testLookup_relative_symlinkLoop()370   public void testLookup_relative_symlinkLoop() {
371     try {
372       lookup("four/loop");
373       fail();
374     } catch (IOException expected) {
375     }
376 
377     try {
378       lookup("four/loop/whatever");
379       fail();
380     } catch (IOException expected) {
381     }
382   }
383 
384   @Test
testLookup_relative_emptyPath()385   public void testLookup_relative_emptyPath() throws IOException {
386     assertExists(lookup(""), "/", "work");
387   }
388 
389   @Test
testLookup_relative_withDotsInPath()390   public void testLookup_relative_withDotsInPath() throws IOException {
391     assertExists(lookup("."), "/", "work");
392     assertExists(lookup("././."), "/", "work");
393     assertExists(lookup("./one/./././two/three"), "two", "three");
394     assertExists(lookup("./one/./././two/././three"), "two", "three");
395     assertExists(lookup("./one/./././two/three/././."), "two", "three");
396   }
397 
398   @Test
testLookup_relative_withDotDotsInPath()399   public void testLookup_relative_withDotDotsInPath() throws IOException {
400     assertExists(lookup(".."), "/", "/");
401     assertExists(lookup("../../.."), "/", "/");
402     assertExists(lookup("../work"), "/", "work");
403     assertExists(lookup("../../work"), "/", "work");
404     assertExists(lookup("../foo"), "/", "foo");
405     assertExists(lookup("../work/one/two/../two/three"), "two", "three");
406     assertExists(lookup("one/two/../../four/../one/two/three/../three"), "two", "three");
407     assertExists(lookup("one/two/three/../../two/three/.."), "one", "two");
408     assertExists(lookup("one/two/three/../../two/three/../.."), "work", "one");
409   }
410 
411   @Test
testLookup_relative_withDotDotsInPath_afterSymlink()412   public void testLookup_relative_withDotDotsInPath_afterSymlink() throws IOException {
413     assertExists(lookup("four/five/.."), "/", "/");
414     assertExists(lookup("four/six/.."), "/", "work");
415   }
416 
lookup(String path, LinkOption... options)417   private DirectoryEntry lookup(String path, LinkOption... options) throws IOException {
418     JimfsPath pathObj = pathService.parsePath(path);
419     return fileTree.lookUp(workingDirectory, pathObj, Options.getLinkOptions(options));
420   }
421 
assertExists(DirectoryEntry entry, String parent, String file)422   private void assertExists(DirectoryEntry entry, String parent, String file) {
423     assertThat(entry.exists()).isTrue();
424     assertThat(entry.name()).isEqualTo(Name.simple(file));
425     assertThat(entry.directory()).isEqualTo(files.get(parent));
426     assertThat(entry.file()).isEqualTo(files.get(file));
427   }
428 
assertParentExists(DirectoryEntry entry, String parent)429   private void assertParentExists(DirectoryEntry entry, String parent) {
430     assertThat(entry.exists()).isFalse();
431     assertThat(entry.directory()).isEqualTo(files.get(parent));
432 
433     try {
434       entry.file();
435       fail();
436     } catch (IllegalStateException expected) {
437     }
438   }
439 
createDirectory(String parent, String name)440   private File createDirectory(String parent, String name) {
441     Directory dir = (Directory) files.get(parent);
442     Directory newFile = Directory.create(new Random().nextInt());
443     dir.link(Name.simple(name), newFile);
444     files.put(name, newFile);
445     return newFile;
446   }
447 
createFile(String parent, String name)448   private File createFile(String parent, String name) {
449     Directory dir = (Directory) files.get(parent);
450     File newFile = regularFile(0);
451     dir.link(Name.simple(name), newFile);
452     files.put(name, newFile);
453     return newFile;
454   }
455 
createSymbolicLink(String parent, String name, String target)456   private File createSymbolicLink(String parent, String name, String target) {
457     Directory dir = (Directory) files.get(parent);
458     File newFile = SymbolicLink.create(new Random().nextInt(), pathService.parsePath(target));
459     dir.link(Name.simple(name), newFile);
460     files.put(name, newFile);
461     return newFile;
462   }
463 }
464