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