1 /* <lambda>null2 * Copyright (C) 2020 Square, 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 package okio 17 18 import kotlin.test.BeforeTest 19 import kotlin.test.Ignore 20 import kotlin.test.Test 21 import kotlin.test.assertContentEquals 22 import kotlin.test.assertEquals 23 import kotlin.test.assertFailsWith 24 import kotlin.test.assertFalse 25 import kotlin.test.assertNull 26 import kotlin.test.assertTrue 27 import kotlin.test.fail 28 import kotlin.time.Duration.Companion.milliseconds 29 import kotlin.time.Duration.Companion.minutes 30 import kotlin.time.Duration.Companion.seconds 31 import okio.ByteString.Companion.encodeUtf8 32 import okio.ByteString.Companion.toByteString 33 import okio.Path.Companion.toPath 34 35 /** This test assumes that okio-files/ is the current working directory when executed. */ 36 abstract class AbstractFileSystemTest( 37 val clock: Clock, 38 val fileSystem: FileSystem, 39 val windowsLimitations: Boolean, 40 val allowClobberingEmptyDirectories: Boolean, 41 val allowAtomicMoveFromFileToDirectory: Boolean, 42 val allowRenameWhenTargetIsOpen: Boolean = !windowsLimitations, 43 temporaryDirectory: Path, 44 ) { 45 val base: Path = temporaryDirectory / "${this::class.simpleName}-${randomToken(16)}" 46 private val isNodeJsFileSystem = fileSystem::class.simpleName?.startsWith("NodeJs") ?: false 47 private val isWasiFileSystem = fileSystem::class.simpleName?.startsWith("Wasi") ?: false 48 private val isWrappingJimFileSystem = this::class.simpleName?.contains("JimFileSystem") ?: false 49 50 @BeforeTest 51 fun setUp() { 52 fileSystem.createDirectories(base) 53 } 54 55 @Test 56 fun doesNotExistsWithInvalidPathDoesNotThrow() { 57 if (isNodeJsFileSystemOnWindows()) return 58 59 val slash = Path.DIRECTORY_SEPARATOR 60 // We are testing: `\\127.0.0.1\..\localhost\c$\Windows`. 61 val file = 62 "${slash}${slash}127.0.0.1$slash..${slash}localhost${slash}c\$${slash}Windows".toPath() 63 64 assertFalse(fileSystem.exists(file)) 65 } 66 67 @Test 68 fun canonicalizeDotReturnsCurrentWorkingDirectory() { 69 if (fileSystem.isFakeFileSystem || fileSystem is ForwardingFileSystem) return 70 val cwd = fileSystem.canonicalize(".".toPath()) 71 val cwdString = cwd.toString() 72 val slash = Path.DIRECTORY_SEPARATOR 73 assertTrue(cwdString) { 74 if (isWrappingJimFileSystem) { 75 cwdString.endsWith("work") 76 } else if (isWasiFileSystem) { 77 cwdString.endsWith("/tmp") 78 } else { 79 cwdString.endsWith("okio${slash}okio") || 80 cwdString.endsWith("${slash}okio-parent-okio-nodefilesystem-test") || 81 cwdString.contains("/CoreSimulator/Devices/") || // iOS simulator. 82 cwdString == "/" // Android emulator. 83 } 84 } 85 } 86 87 @Test 88 fun currentWorkingDirectoryIsADirectory() { 89 val metadata = fileSystem.metadata(".".toPath()) 90 assertTrue(metadata.isDirectory) 91 assertFalse(metadata.isRegularFile) 92 } 93 94 @Test 95 fun canonicalizeAbsolutePathNoSuchFile() { 96 assertFailsWith<FileNotFoundException> { 97 fileSystem.canonicalize(base / "no-such-file") 98 } 99 } 100 101 @Test 102 fun canonicalizeRelativePathNoSuchFile() { 103 assertFailsWith<FileNotFoundException> { 104 fileSystem.canonicalize("no-such-file".toPath()) 105 } 106 } 107 108 @Test 109 fun canonicalizeFollowsSymlinkDirectories() { 110 if (!supportsSymlink()) return 111 val base = fileSystem.canonicalize(base) 112 113 fileSystem.createDirectory(base / "real-directory") 114 115 val expected = base / "real-directory" / "real-file.txt" 116 expected.writeUtf8("hello") 117 118 fileSystem.createSymlink(base / "symlink-directory", base / "real-directory") 119 120 val canonicalPath = fileSystem.canonicalize(base / "symlink-directory" / "real-file.txt") 121 assertEquals(expected, canonicalPath) 122 } 123 124 @Test 125 fun canonicalizeFollowsSymlinkFiles() { 126 if (!supportsSymlink()) return 127 val base = fileSystem.canonicalize(base) 128 129 fileSystem.createDirectory(base / "real-directory") 130 131 val expected = base / "real-directory" / "real-file.txt" 132 expected.writeUtf8("hello") 133 134 fileSystem.createSymlink( 135 base / "real-directory" / "symlink-file.txt", 136 expected, 137 ) 138 139 val canonicalPath = fileSystem.canonicalize(base / "real-directory" / "symlink-file.txt") 140 assertEquals(expected, canonicalPath) 141 } 142 143 @Test 144 fun canonicalizeFollowsMultipleDirectoriesAndMultipleFiles() { 145 if (!supportsSymlink()) return 146 val base = fileSystem.canonicalize(base) 147 148 fileSystem.createDirectory(base / "real-directory") 149 150 val expected = base / "real-directory" / "real-file.txt" 151 expected.writeUtf8("hello") 152 153 fileSystem.createSymlink( 154 base / "real-directory" / "one-symlink-file.txt", 155 expected, 156 ) 157 158 fileSystem.createSymlink( 159 base / "real-directory" / "two-symlink-file.txt", 160 base / "real-directory" / "one-symlink-file.txt", 161 ) 162 163 fileSystem.createSymlink( 164 base / "one-symlink-directory", 165 base / "real-directory", 166 ) 167 168 fileSystem.createSymlink( 169 base / "two-symlink-directory", 170 base / "one-symlink-directory", 171 ) 172 173 assertEquals( 174 expected, 175 fileSystem.canonicalize(base / "two-symlink-directory" / "two-symlink-file.txt"), 176 ) 177 assertEquals( 178 expected, 179 fileSystem.canonicalize(base / "two-symlink-directory" / "one-symlink-file.txt"), 180 ) 181 assertEquals( 182 expected, 183 fileSystem.canonicalize(base / "two-symlink-directory" / "real-file.txt"), 184 ) 185 186 assertEquals( 187 expected, 188 fileSystem.canonicalize(base / "one-symlink-directory" / "two-symlink-file.txt"), 189 ) 190 assertEquals( 191 expected, 192 fileSystem.canonicalize(base / "one-symlink-directory" / "one-symlink-file.txt"), 193 ) 194 assertEquals( 195 expected, 196 fileSystem.canonicalize(base / "one-symlink-directory" / "real-file.txt"), 197 ) 198 199 assertEquals( 200 expected, 201 fileSystem.canonicalize(base / "real-directory" / "two-symlink-file.txt"), 202 ) 203 assertEquals( 204 expected, 205 fileSystem.canonicalize(base / "real-directory" / "one-symlink-file.txt"), 206 ) 207 assertEquals( 208 expected, 209 fileSystem.canonicalize(expected), 210 ) 211 } 212 213 @Test 214 fun canonicalizeReturnsDeeperPath() { 215 if (!supportsSymlink()) return 216 val base = fileSystem.canonicalize(base) 217 218 fileSystem.createDirectories(base / "a" / "b" / "c") 219 220 val expected = base / "a" / "b" / "c" / "d.txt" 221 expected.writeUtf8("hello") 222 223 fileSystem.createSymlink( 224 base / "e.txt", 225 "a".toPath() / "b" / "c" / "d.txt", 226 ) 227 228 assertEquals( 229 expected, 230 fileSystem.canonicalize(base / "e.txt"), 231 ) 232 } 233 234 @Test 235 fun canonicalizeReturnsShallowerPath() { 236 if (!supportsSymlink()) return 237 val base = fileSystem.canonicalize(base) 238 239 val expected = base / "a.txt" 240 expected.writeUtf8("hello") 241 242 fileSystem.createDirectories(base / "b" / "c" / "d") 243 fileSystem.createSymlink( 244 base / "b" / "c" / "d" / "e.txt", 245 "../".toPath() / ".." / ".." / "a.txt", 246 ) 247 248 assertEquals( 249 expected, 250 fileSystem.canonicalize(base / "b" / "c" / "d" / "e.txt"), 251 ) 252 } 253 254 @Test 255 fun list() { 256 val target = base / "list" 257 target.writeUtf8("hello, world!") 258 val entries = fileSystem.list(base) 259 assertTrue(entries.toString()) { target in entries } 260 } 261 262 @Test 263 fun listOnRelativePathReturnsRelativePaths() { 264 // Make sure there's always at least one file so our assertion is useful. 265 if (fileSystem.isFakeFileSystem) { 266 val workingDirectory = "/directory".toPath() 267 fileSystem.createDirectory(workingDirectory) 268 fileSystem.workingDirectory = workingDirectory 269 fileSystem.write("a.txt".toPath()) { 270 writeUtf8("hello, world!") 271 } 272 } else if (isWrappingJimFileSystem || isWasiFileSystem) { 273 fileSystem.write("a.txt".toPath()) { 274 writeUtf8("hello, world!") 275 } 276 } 277 278 val entries = fileSystem.list(".".toPath()) 279 assertTrue(entries.toString()) { entries.isNotEmpty() && entries.all { it.isRelative } } 280 } 281 282 @Test 283 fun listOnRelativePathWhichIsNotDotReturnsRelativePaths() { 284 if (isNodeJsFileSystem) return 285 286 // Make sure there's always at least one file so our assertion is useful. We copy the first 2 287 // entries of the real working directory of the JVM to validate the results on all environment. 288 if ( 289 fileSystem.isFakeFileSystem || 290 fileSystem is ForwardingFileSystem && fileSystem.delegate.isFakeFileSystem 291 ) { 292 val workingDirectory = "/directory".toPath() 293 fileSystem.createDirectory(workingDirectory) 294 fileSystem.workingDirectory = workingDirectory 295 val apiDir = "api".toPath() 296 fileSystem.createDirectory(apiDir) 297 fileSystem.write(apiDir / "okio.api".toPath()) { 298 writeUtf8("hello, world!") 299 } 300 } else if (isWrappingJimFileSystem || isWasiFileSystem) { 301 val apiDir = "api".toPath() 302 fileSystem.createDirectory(apiDir) 303 fileSystem.write(apiDir / "okio.api".toPath()) { 304 writeUtf8("hello, world!") 305 } 306 } 307 308 try { 309 assertEquals( 310 listOf("api".toPath() / "okio.api".toPath()), 311 fileSystem.list("api".toPath()), 312 // List some entries to help debugging. 313 fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(), 314 ) 315 } catch (e: Throwable) { 316 if (e !is AssertionError && e !is FileNotFoundException) { throw e } 317 318 // Non JVM environments. 319 val firstChild = fileSystem.list("Library".toPath()).first() 320 assertTrue( 321 // List some entries to help debugging. 322 fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(), 323 ) { 324 // To avoid relying too much on the environment we check that the path contains its parent 325 // once and that it's relative. 326 firstChild.isRelative && 327 firstChild.toString().startsWith("Library") && 328 firstChild.toString().split("Library").size == 2 329 } 330 } 331 } 332 333 @Test 334 fun listOrNullOnRelativePathWhichIsNotDotReturnsRelativePaths() { 335 if (isNodeJsFileSystem) return 336 337 // Make sure there's always at least one file so our assertion is useful. We copy the first 2 338 // entries of the real working directory of the JVM to validate the results on all environment. 339 if ( 340 fileSystem.isFakeFileSystem || 341 fileSystem is ForwardingFileSystem && fileSystem.delegate.isFakeFileSystem 342 ) { 343 val workingDirectory = "/directory".toPath() 344 fileSystem.createDirectory(workingDirectory) 345 fileSystem.workingDirectory = workingDirectory 346 val apiDir = "api".toPath() 347 fileSystem.createDirectory(apiDir) 348 fileSystem.write(apiDir / "okio.api".toPath()) { 349 writeUtf8("hello, world!") 350 } 351 } else if (isWrappingJimFileSystem) { 352 val apiDir = "api".toPath() 353 fileSystem.createDirectory(apiDir) 354 fileSystem.write(apiDir / "okio.api".toPath()) { 355 writeUtf8("hello, world!") 356 } 357 } 358 359 try { 360 assertEquals( 361 listOf("api".toPath() / "okio.api".toPath()), 362 fileSystem.listOrNull("api".toPath()), 363 // List some entries to help debugging. 364 fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(), 365 ) 366 } catch (e: Throwable) { 367 if (e !is AssertionError && e !is FileNotFoundException) { throw e } 368 369 // Non JVM environments. 370 val firstChild = fileSystem.list("Library".toPath()).first() 371 assertTrue( 372 // List some entries to help debugging. 373 fileSystem.listRecursively(".".toPath()).take(5).toList().joinToString(), 374 ) { 375 // To avoid relying too much on the environment we check that the path contains its parent 376 // once and that it's relative. 377 firstChild.isRelative && 378 firstChild.toString().startsWith("Library") && 379 firstChild.toString().split("Library").size == 2 380 } 381 } 382 } 383 384 @Test 385 fun listResultsAreSorted() { 386 val fileA = base / "a" 387 val fileB = base / "b" 388 val fileC = base / "c" 389 val fileD = base / "d" 390 391 // Create files in a different order than the sorted order, so a file system that returns files 392 // in creation-order or reverse-creation order won't pass by accident. 393 fileD.writeUtf8("fileD") 394 fileB.writeUtf8("fileB") 395 fileC.writeUtf8("fileC") 396 fileA.writeUtf8("fileA") 397 398 val entries = fileSystem.list(base) 399 assertEquals(entries, listOf(fileA, fileB, fileC, fileD)) 400 } 401 402 @Test 403 fun listNoSuchDirectory() { 404 assertFailsWith<FileNotFoundException> { 405 fileSystem.list(base / "no-such-directory") 406 } 407 } 408 409 @Test 410 fun listFile() { 411 val target = base / "list" 412 target.writeUtf8("hello, world!") 413 val exception = assertFailsWith<IOException> { 414 fileSystem.list(target) 415 } 416 assertTrue(exception !is FileNotFoundException) 417 } 418 419 @Test 420 fun listOrNull() { 421 val target = base / "list" 422 target.writeUtf8("hello, world!") 423 val entries = fileSystem.listOrNull(base)!! 424 assertTrue(entries.toString()) { target in entries } 425 } 426 427 @Test 428 fun listOrNullOnRelativePathReturnsRelativePaths() { 429 // Make sure there's always at least one file so our assertion is useful. 430 if (fileSystem.isFakeFileSystem) { 431 val workingDirectory = "/directory".toPath() 432 fileSystem.createDirectory(workingDirectory) 433 fileSystem.workingDirectory = workingDirectory 434 fileSystem.write("a.txt".toPath()) { 435 writeUtf8("hello, world!") 436 } 437 } else if (isWrappingJimFileSystem) { 438 fileSystem.write("a.txt".toPath()) { 439 writeUtf8("hello, world!") 440 } 441 } 442 443 val entries = fileSystem.listOrNull(".".toPath()) 444 assertTrue(entries.toString()) { entries!!.isNotEmpty() && entries.all { it.isRelative } } 445 } 446 447 @Test 448 fun listOrNullResultsAreSorted() { 449 val fileA = base / "a" 450 val fileB = base / "b" 451 val fileC = base / "c" 452 val fileD = base / "d" 453 454 // Create files in a different order than the sorted order, so a file system that returns files 455 // in creation-order or reverse-creation order won't pass by accident. 456 fileD.writeUtf8("fileD") 457 fileB.writeUtf8("fileB") 458 fileC.writeUtf8("fileC") 459 fileA.writeUtf8("fileA") 460 461 val entries = fileSystem.listOrNull(base) 462 assertEquals(entries, listOf(fileA, fileB, fileC, fileD)) 463 } 464 465 @Test 466 fun listOrNullNoSuchDirectory() { 467 assertNull(fileSystem.listOrNull(base / "no-such-directory")) 468 } 469 470 @Test 471 fun listOrNullFile() { 472 val target = base / "list" 473 target.writeUtf8("hello, world!") 474 assertNull(fileSystem.listOrNull(target)) 475 } 476 477 @Test 478 fun listRecursivelyReturnsEmpty() { 479 val entries = fileSystem.listRecursively(base) 480 assertEquals(entries.toList(), listOf()) 481 } 482 483 @Test 484 fun listRecursivelyReturnsSingleFile() { 485 val baseA = base / "a" 486 baseA.writeUtf8("a") 487 val entries = fileSystem.listRecursively(base) 488 assertEquals(entries.toList(), listOf(baseA)) 489 } 490 491 @Test 492 fun listRecursivelyRecurses() { 493 val baseA = base / "a" 494 val baseAB = baseA / "b" 495 baseA.createDirectory() 496 baseAB.writeUtf8("ab") 497 val entries = fileSystem.listRecursively(base) 498 assertEquals(listOf(baseA, baseAB), entries.toList()) 499 } 500 501 @Test 502 fun listRecursivelyNoSuchFile() { 503 val baseA = base / "a" 504 val sequence = fileSystem.listRecursively(baseA) 505 assertFailsWith<FileNotFoundException> { 506 sequence.first() 507 } 508 } 509 510 /** 511 * Note that this is different from `Files.walk` in java.nio which returns the argument even if 512 * it is not a directory. 513 */ 514 @Test 515 fun listRecursivelyNotADirectory() { 516 val baseA = base / "a" 517 baseA.writeUtf8("a") 518 val sequence = fileSystem.listRecursively(baseA) 519 val exception = assertFailsWith<IOException> { 520 sequence.first() 521 } 522 assertTrue(exception !is FileNotFoundException) 523 } 524 525 @Test 526 fun listRecursivelyIsDepthFirst() { 527 val baseA = base / "a" 528 val baseB = base / "b" 529 val baseA1 = baseA / "1" 530 val baseA2 = baseA / "2" 531 val baseB1 = baseB / "1" 532 val baseB2 = baseB / "2" 533 baseA.createDirectory() 534 baseB.createDirectory() 535 baseA1.writeUtf8("a1") 536 baseA2.writeUtf8("a2") 537 baseB1.writeUtf8("b1") 538 baseB2.writeUtf8("b2") 539 val entries = fileSystem.listRecursively(base) 540 assertEquals(listOf(baseA, baseA1, baseA2, baseB, baseB1, baseB2), entries.toList()) 541 } 542 543 @Test 544 fun listRecursivelyIsLazy() { 545 val baseA = base / "a" 546 val baseB = base / "b" 547 baseA.createDirectory() 548 baseB.createDirectory() 549 val entries = fileSystem.listRecursively(base).iterator() 550 551 // This call will enqueue up the children of base, baseA and baseB. 552 assertEquals(baseA, entries.next()) 553 val baseA1 = baseA / "1" 554 val baseA2 = baseA / "2" 555 baseA1.writeUtf8("a1") 556 baseA2.writeUtf8("a2") 557 558 // This call will enqueue the children of baseA, baseA1 and baseA2. 559 assertEquals(baseA1, entries.next()) 560 assertEquals(baseA2, entries.next()) 561 assertEquals(baseB, entries.next()) 562 563 // This call will enqueue the children of baseB, baseB1 and baseB2. 564 val baseB1 = baseB / "1" 565 val baseB2 = baseB / "2" 566 baseB1.writeUtf8("b1") 567 baseB2.writeUtf8("b2") 568 assertEquals(baseB1, entries.next()) 569 assertEquals(baseB2, entries.next()) 570 assertFalse(entries.hasNext()) 571 } 572 573 /** 574 * This test creates directories that should be listed lazily, and then deletes them! The test 575 * wants to confirm that the sequence is resilient to such changes. 576 */ 577 @Test 578 fun listRecursivelySilentlyIgnoresListFailures() { 579 val baseA = base / "a" 580 val baseB = base / "b" 581 baseA.createDirectory() 582 baseB.createDirectory() 583 val entries = fileSystem.listRecursively(base).iterator() 584 assertEquals(baseA, entries.next()) 585 assertEquals(baseB, entries.next()) 586 fileSystem.delete(baseA) 587 fileSystem.delete(baseB) 588 assertFalse(entries.hasNext()) 589 } 590 591 @Test 592 fun listRecursivelySequenceIterationsAreIndependent() { 593 val sequence = fileSystem.listRecursively(base) 594 val iterator1 = sequence.iterator() 595 assertFalse(iterator1.hasNext()) 596 val baseA = base / "a" 597 baseA.writeUtf8("a") 598 val iterator2 = sequence.iterator() 599 assertEquals(baseA, iterator2.next()) 600 assertFalse(iterator2.hasNext()) 601 } 602 603 @Test 604 fun listRecursivelyFollowsSymlinks() { 605 if (!supportsSymlink()) return 606 607 val baseA = base / "a" 608 val baseAA = baseA / "a" 609 val baseB = base / "b" 610 val baseBA = baseB / "a" 611 baseA.createDirectory() 612 baseAA.writeUtf8("aa") 613 fileSystem.createSymlink(baseB, baseA) 614 615 val sequence = fileSystem.listRecursively(base, followSymlinks = true) 616 assertEquals(listOf(baseA, baseAA, baseB, baseBA), sequence.toList()) 617 } 618 619 @Test 620 fun listRecursivelyDoesNotFollowSymlinks() { 621 if (!supportsSymlink()) return 622 623 val baseA = base / "a" 624 val baseAA = baseA / "a" 625 val baseB = base / "b" 626 baseA.createDirectory() 627 baseAA.writeUtf8("aa") 628 fileSystem.createSymlink(baseB, baseA) 629 630 val sequence = fileSystem.listRecursively(base, followSymlinks = false) 631 assertEquals(listOf(baseA, baseAA, baseB), sequence.toList()) 632 } 633 634 @Test 635 fun listRecursivelyOnSymlink() { 636 if (!supportsSymlink()) return 637 638 val baseA = base / "a" 639 val baseAA = baseA / "a" 640 val baseB = base / "b" 641 val baseBA = baseB / "a" 642 643 baseA.createDirectory() 644 baseAA.writeUtf8("aa") 645 fileSystem.createSymlink(baseB, baseA) 646 647 val sequence = fileSystem.listRecursively(baseB, followSymlinks = false) 648 assertEquals(listOf(baseBA), sequence.toList()) 649 } 650 651 @Test 652 fun listRecursiveWithSpecialCharacterNamedFiles() { 653 val baseA = base / "ä" 654 val baseASuperSaiyan = baseA / "超サイヤ人" 655 val baseB = base / "ß" 656 val baseBIliad = baseB / "Ἰλιάς" 657 658 baseA.createDirectory() 659 baseASuperSaiyan.writeUtf8("カカロットよ!") 660 baseB.createDirectory() 661 baseBIliad.writeUtf8("μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος") 662 663 val sequence = fileSystem.listRecursively(base) 664 assertEquals(listOf(baseB, baseBIliad, baseA, baseASuperSaiyan), sequence.toList()) 665 } 666 667 @Test 668 fun listRecursiveOnSymlinkWithSpecialCharacterNamedFiles() { 669 if (!supportsSymlink()) return 670 671 val baseA = base / "ä" 672 val baseASuperSaiyan = baseA / "超サイヤ人" 673 val baseB = base / "ß" 674 val baseBSuperSaiyan = baseB / "超サイヤ人" 675 676 baseA.createDirectory() 677 baseASuperSaiyan.writeUtf8("aa") 678 fileSystem.createSymlink(baseB, baseA) 679 680 val sequence = fileSystem.listRecursively(baseB, followSymlinks = false) 681 assertEquals(listOf(baseBSuperSaiyan), sequence.toList()) 682 } 683 684 @Test 685 fun listRecursivelyOnSymlinkCycleThrows() { 686 if (!supportsSymlink()) return 687 688 val baseA = base / "a" 689 val baseAB = baseA / "b" 690 val baseAC = baseA / "c" 691 692 baseA.createDirectory() 693 baseAB.writeUtf8("ab") 694 fileSystem.createSymlink(baseAC, baseA) 695 696 val iterator = fileSystem.listRecursively(base, followSymlinks = true).iterator() 697 assertEquals(baseA, iterator.next()) 698 assertEquals(baseAB, iterator.next()) 699 assertEquals(baseAC, iterator.next()) 700 val exception = assertFailsWith<IOException> { 701 iterator.next() // This would fail because 'c' refers to a path we've already visited. 702 } 703 assertEquals("symlink cycle at $baseAC", exception.message) 704 } 705 706 @Test 707 fun listRecursivelyDoesNotFollowRelativeSymlink() { 708 if (!supportsSymlink()) return 709 710 val baseA = base / "a" 711 val baseAA = baseA / "a" 712 val baseB = base / "b" 713 baseA.createDirectory() 714 baseAA.writeUtf8("aa") 715 fileSystem.createSymlink(baseB, ".".toPath()) // Symlink to enclosing directory! 716 717 val iterator = fileSystem.listRecursively(base, followSymlinks = true).iterator() 718 assertEquals(baseA, iterator.next()) 719 assertEquals(baseAA, iterator.next()) 720 assertEquals(baseB, iterator.next()) 721 val exception = assertFailsWith<IOException> { 722 iterator.next() 723 } 724 assertEquals("symlink cycle at $baseB", exception.message) 725 } 726 727 @Test 728 fun fileSourceNoSuchDirectory() { 729 assertFailsWith<FileNotFoundException> { 730 fileSystem.source(base / "no-such-directory" / "file") 731 } 732 } 733 734 @Test 735 fun fileSource() { 736 val path = base / "file-source" 737 path.writeUtf8("hello, world!") 738 739 val source = fileSystem.source(path) 740 val buffer = Buffer() 741 assertTrue(source.read(buffer, 100L) == 13L) 742 assertEquals(-1L, source.read(buffer, 100L)) 743 assertEquals("hello, world!", buffer.readUtf8()) 744 source.close() 745 } 746 747 @Test 748 fun readPath() { 749 val path = base / "read-path" 750 val string = "hello, read with a Path" 751 path.writeUtf8(string) 752 753 val result = fileSystem.read(path) { 754 assertEquals("hello", readUtf8(5)) 755 assertEquals(", read with ", readUtf8(12)) 756 assertEquals("a Path", readUtf8()) 757 return@read "success" 758 } 759 assertEquals("success", result) 760 } 761 762 @Test 763 fun fileSink() { 764 val path = base / "file-sink" 765 val sink = fileSystem.sink(path) 766 val buffer = Buffer().writeUtf8("hello, world!") 767 sink.write(buffer, buffer.size) 768 sink.close() 769 assertTrue(path in fileSystem.list(base)) 770 assertEquals(0, buffer.size) 771 assertEquals("hello, world!", path.readUtf8()) 772 } 773 774 @Test 775 fun fileSinkWithSpecialCharacterNamedFiles() { 776 val path = base / "Ἰλιάς" 777 val sink = fileSystem.sink(path) 778 val buffer = Buffer().writeUtf8("μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος") 779 sink.write(buffer, buffer.size) 780 sink.close() 781 assertTrue(path in fileSystem.list(base)) 782 assertEquals(0, buffer.size) 783 assertEquals("μῆνιν ἄειδε θεὰ Πηληϊάδεω Ἀχιλῆος", path.readUtf8()) 784 } 785 786 @Test 787 fun fileSinkMustCreate() { 788 val path = base / "file-sink" 789 val sink = fileSystem.sink(path, mustCreate = true) 790 val buffer = Buffer().writeUtf8("hello, world!") 791 sink.write(buffer, buffer.size) 792 sink.close() 793 assertTrue(path in fileSystem.list(base)) 794 assertEquals(0, buffer.size) 795 assertEquals("hello, world!", path.readUtf8()) 796 } 797 798 @Test 799 fun fileSinkMustCreateThrowsIfAlreadyExists() { 800 val path = base / "file-sink" 801 path.writeUtf8("First!") 802 assertFailsWith<IOException> { 803 fileSystem.sink(path, mustCreate = true) 804 } 805 } 806 807 /** 808 * Write a file by concatenating three mechanisms, then read it in its entirety using three other 809 * mechanisms. This is attempting to defend against unwanted use of Windows text mode. 810 * 811 * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-wfopen?view=msvc-160 812 */ 813 @Test 814 fun fileSinkSpecialCharacters() { 815 val path = base / "file-sink-special-characters" 816 val content = "[ctrl-z: \u001A][newline: \n][crlf: \r\n]".encodeUtf8() 817 818 fileSystem.write(path) { 819 writeUtf8("FileSystem.write()\n") 820 write(content) 821 } 822 823 fileSystem.openReadWrite(path).use { handle -> 824 handle.sink(fileOffset = handle.size()).buffer().use { sink -> 825 sink.writeUtf8("FileSystem.openReadWrite()\n") 826 sink.write(content) 827 } 828 } 829 830 fileSystem.appendingSink(path).buffer().use { sink -> 831 sink.writeUtf8("FileSystem.appendingSink()\n") 832 sink.write(content) 833 } 834 835 fileSystem.read(path) { 836 assertEquals("FileSystem.write()", readUtf8LineStrict()) 837 assertEquals(content, readByteString(content.size.toLong())) 838 assertEquals("FileSystem.openReadWrite()", readUtf8LineStrict()) 839 assertEquals(content, readByteString(content.size.toLong())) 840 assertEquals("FileSystem.appendingSink()", readUtf8LineStrict()) 841 assertEquals(content, readByteString(content.size.toLong())) 842 assertTrue(exhausted()) 843 } 844 845 fileSystem.openReadWrite(path).use { handle -> 846 handle.source().buffer().use { source -> 847 assertEquals("FileSystem.write()", source.readUtf8LineStrict()) 848 assertEquals(content, source.readByteString(content.size.toLong())) 849 assertEquals("FileSystem.openReadWrite()", source.readUtf8LineStrict()) 850 assertEquals(content, source.readByteString(content.size.toLong())) 851 assertEquals("FileSystem.appendingSink()", source.readUtf8LineStrict()) 852 assertEquals(content, source.readByteString(content.size.toLong())) 853 assertTrue(source.exhausted()) 854 } 855 } 856 857 fileSystem.openReadOnly(path).use { handle -> 858 handle.source().buffer().use { source -> 859 assertEquals("FileSystem.write()", source.readUtf8LineStrict()) 860 assertEquals(content, source.readByteString(content.size.toLong())) 861 assertEquals("FileSystem.openReadWrite()", source.readUtf8LineStrict()) 862 assertEquals(content, source.readByteString(content.size.toLong())) 863 assertEquals("FileSystem.appendingSink()", source.readUtf8LineStrict()) 864 assertEquals(content, source.readByteString(content.size.toLong())) 865 assertTrue(source.exhausted()) 866 } 867 } 868 } 869 870 @Test 871 fun writePath() { 872 val path = base / "write-path" 873 val content = fileSystem.write(path) { 874 val string = "hello, write with a Path" 875 writeUtf8(string) 876 return@write string 877 } 878 assertTrue(path in fileSystem.list(base)) 879 assertEquals(content, path.readUtf8()) 880 } 881 882 @Test 883 fun writePathMustCreate() { 884 val path = base / "write-path" 885 val content = fileSystem.write(path, mustCreate = true) { 886 val string = "hello, write with a Path" 887 writeUtf8(string) 888 return@write string 889 } 890 assertTrue(path in fileSystem.list(base)) 891 assertEquals(content, path.readUtf8()) 892 } 893 894 @Test 895 fun writePathMustCreateThrowsIfAlreadyExists() { 896 val path = base / "write-path" 897 path.writeUtf8("First!") 898 assertFailsWith<IOException> { 899 fileSystem.write(path, mustCreate = true) {} 900 } 901 } 902 903 @Test 904 fun appendingSinkAppendsToExistingFile() { 905 val path = base / "appending-sink-appends-to-existing-file" 906 path.writeUtf8("hello, world!\n") 907 val sink = fileSystem.appendingSink(path) 908 val buffer = Buffer().writeUtf8("this is added later!") 909 sink.write(buffer, buffer.size) 910 sink.close() 911 assertTrue(path in fileSystem.list(base)) 912 assertEquals("hello, world!\nthis is added later!", path.readUtf8()) 913 } 914 915 @Test 916 fun appendingSinkDoesNotImpactExistingFile() { 917 if (fileSystem.isFakeFileSystem && !fileSystem.allowReadsWhileWriting) return 918 919 val path = base / "appending-sink-does-not-impact-existing-file" 920 path.writeUtf8("hello, world!\n") 921 val sink = fileSystem.appendingSink(path) 922 assertEquals("hello, world!\n", path.readUtf8()) 923 sink.close() 924 assertEquals("hello, world!\n", path.readUtf8()) 925 } 926 927 @Test 928 fun appendingSinkCreatesNewFile() { 929 val path = base / "appending-sink-creates-new-file" 930 val sink = fileSystem.appendingSink(path) 931 val buffer = Buffer().writeUtf8("this is all there is!") 932 sink.write(buffer, buffer.size) 933 sink.close() 934 assertTrue(path in fileSystem.list(base)) 935 assertEquals("this is all there is!", path.readUtf8()) 936 } 937 938 @Test 939 fun appendingSinkExistingFileMustExist() { 940 val path = base / "appending-sink-creates-new-file" 941 path.writeUtf8("Hey, ") 942 943 val sink = fileSystem.appendingSink(path, mustExist = true) 944 val buffer = Buffer().writeUtf8("this is all there is!") 945 sink.write(buffer, buffer.size) 946 sink.close() 947 assertTrue(path in fileSystem.list(base)) 948 assertEquals("Hey, this is all there is!", path.readUtf8()) 949 } 950 951 @Test 952 fun appendingSinkMustExistThrowsIfAbsent() { 953 val path = base / "appending-sink-creates-new-file" 954 assertFailsWith<IOException> { 955 fileSystem.appendingSink(path, mustExist = true) 956 } 957 } 958 959 @Test 960 fun fileSinkFlush() { 961 if (fileSystem.isFakeFileSystem && !fileSystem.allowReadsWhileWriting) return 962 963 val path = base / "file-sink" 964 val sink = fileSystem.sink(path) 965 966 val buffer = Buffer().writeUtf8("hello,") 967 sink.write(buffer, buffer.size) 968 sink.flush() 969 assertEquals("hello,", path.readUtf8()) 970 971 buffer.writeUtf8(" world!") 972 sink.write(buffer, buffer.size) 973 sink.close() 974 assertEquals("hello, world!", path.readUtf8()) 975 } 976 977 @Test 978 fun fileSinkNoSuchDirectory() { 979 assertFailsWith<FileNotFoundException> { 980 fileSystem.sink(base / "no-such-directory" / "file") 981 } 982 } 983 984 @Test 985 fun createDirectory() { 986 val path = base / "create-directory" 987 fileSystem.createDirectory(path) 988 assertTrue(path in fileSystem.list(base)) 989 } 990 991 @Test 992 fun createDirectoryMustCreate() { 993 val path = base / "create-directory" 994 fileSystem.createDirectory(path, mustCreate = true) 995 assertTrue(path in fileSystem.list(base)) 996 } 997 998 @Test 999 fun createDirectoryAlreadyExists() { 1000 val path = base / "already-exists" 1001 fileSystem.createDirectory(path) 1002 fileSystem.createDirectory(path) 1003 } 1004 1005 @Test 1006 fun createDirectoryAlreadyExistsMustCreateThrows() { 1007 val path = base / "already-exists" 1008 fileSystem.createDirectory(path) 1009 val exception = assertFailsWith<IOException> { 1010 fileSystem.createDirectory(path, mustCreate = true) 1011 } 1012 assertTrue(exception !is FileNotFoundException) 1013 } 1014 1015 @Test 1016 fun createDirectoryParentDirectoryDoesNotExist() { 1017 val path = base / "no-such-directory" / "created" 1018 assertFailsWith<IOException> { 1019 fileSystem.createDirectory(path) 1020 } 1021 } 1022 1023 @Test 1024 fun createDirectoriesSingle() { 1025 val path = base / "create-directories-single" 1026 fileSystem.createDirectories(path) 1027 assertTrue(path in fileSystem.list(base)) 1028 assertTrue(fileSystem.metadata(path).isDirectory) 1029 } 1030 1031 @Test 1032 fun createDirectoriesAlreadyExists() { 1033 val path = base / "already-exists" 1034 fileSystem.createDirectory(path) 1035 fileSystem.createDirectories(path) 1036 assertTrue(fileSystem.metadata(path).isDirectory) 1037 } 1038 1039 @Test 1040 fun createDirectoriesAlreadyExistsMustCreateThrows() { 1041 val path = base / "already-exists" 1042 fileSystem.createDirectory(path) 1043 val exception = assertFailsWith<IOException> { 1044 fileSystem.createDirectories(path, mustCreate = true) 1045 } 1046 assertTrue(exception !is FileNotFoundException) 1047 assertTrue(fileSystem.metadata(path).isDirectory) 1048 } 1049 1050 @Test 1051 fun createDirectoriesParentDirectoryDoesNotExist() { 1052 fileSystem.createDirectories(base / "a" / "b" / "c") 1053 assertTrue(base / "a" in fileSystem.list(base)) 1054 assertTrue(base / "a" / "b" in fileSystem.list(base / "a")) 1055 assertTrue(base / "a" / "b" / "c" in fileSystem.list(base / "a" / "b")) 1056 assertTrue(fileSystem.metadata(base / "a" / "b" / "c").isDirectory) 1057 } 1058 1059 @Test 1060 fun createDirectoriesParentIsFile() { 1061 val file = base / "simple-file" 1062 file.writeUtf8("just a file") 1063 assertFailsWith<IOException> { 1064 fileSystem.createDirectories(file / "child") 1065 } 1066 } 1067 1068 @Test 1069 fun atomicMoveFile() { 1070 val source = base / "source" 1071 source.writeUtf8("hello, world!") 1072 val target = base / "target" 1073 fileSystem.atomicMove(source, target) 1074 assertEquals("hello, world!", target.readUtf8()) 1075 assertTrue(source !in fileSystem.list(base)) 1076 assertTrue(target in fileSystem.list(base)) 1077 } 1078 1079 @Test 1080 fun atomicMoveDirectory() { 1081 val source = base / "source" 1082 fileSystem.createDirectory(source) 1083 val target = base / "target" 1084 fileSystem.atomicMove(source, target) 1085 assertTrue(source !in fileSystem.list(base)) 1086 assertTrue(target in fileSystem.list(base)) 1087 } 1088 1089 @Test 1090 fun atomicMoveSourceIsTarget() { 1091 val source = base / "source" 1092 source.writeUtf8("hello, world!") 1093 fileSystem.atomicMove(source, source) 1094 assertEquals("hello, world!", source.readUtf8()) 1095 assertTrue(source in fileSystem.list(base)) 1096 } 1097 1098 @Test 1099 fun atomicMoveClobberExistingFile() { 1100 // `java.io` on Windows doesn't allow file renaming if the target already exists. 1101 if (isJvmFileSystemOnWindows()) return 1102 1103 val source = base / "source" 1104 source.writeUtf8("hello, world!") 1105 val target = base / "target" 1106 target.writeUtf8("this file will be clobbered!") 1107 fileSystem.atomicMove(source, target) 1108 assertEquals("hello, world!", target.readUtf8()) 1109 assertTrue(source !in fileSystem.list(base)) 1110 assertTrue(target in fileSystem.list(base)) 1111 } 1112 1113 @Test 1114 fun atomicMoveSourceDoesNotExist() { 1115 val source = base / "source" 1116 val target = base / "target" 1117 if (fileSystem::class.simpleName == "JvmSystemFileSystem") { 1118 assertFailsWith<IOException> { 1119 fileSystem.atomicMove(source, target) 1120 } 1121 } else { 1122 assertFailsWith<FileNotFoundException> { 1123 fileSystem.atomicMove(source, target) 1124 } 1125 } 1126 } 1127 1128 @Test 1129 fun atomicMoveSourceIsFileAndTargetIsDirectory() { 1130 val source = base / "source" 1131 source.writeUtf8("hello, world!") 1132 val target = base / "target" 1133 fileSystem.createDirectory(target) 1134 1135 if (allowAtomicMoveFromFileToDirectory) { 1136 fileSystem.atomicMove(source, target) 1137 assertEquals("hello, world!", target.readUtf8()) 1138 } else { 1139 val exception = assertFailsWith<IOException> { 1140 fileSystem.atomicMove(source, target) 1141 } 1142 assertTrue(exception !is FileNotFoundException) 1143 } 1144 } 1145 1146 @Test 1147 fun atomicMoveSourceIsDirectoryAndTargetIsFile() { 1148 // `java.io` on Windows doesn't allow file renaming if the target already exists. 1149 if (isJvmFileSystemOnWindows()) return 1150 1151 val source = base / "source" 1152 fileSystem.createDirectory(source) 1153 val target = base / "target" 1154 target.writeUtf8("hello, world!") 1155 try { 1156 fileSystem.atomicMove(source, target) 1157 assertTrue(allowClobberingEmptyDirectories) 1158 } catch (e: IOException) { 1159 assertFalse(allowClobberingEmptyDirectories) 1160 } 1161 } 1162 1163 @Test 1164 fun copyFile() { 1165 val source = base / "source" 1166 source.writeUtf8("hello, world!") 1167 val target = base / "target" 1168 fileSystem.copy(source, target) 1169 assertTrue(target in fileSystem.list(base)) 1170 assertEquals("hello, world!", source.readUtf8()) 1171 assertEquals("hello, world!", target.readUtf8()) 1172 } 1173 1174 @Test 1175 fun copySourceDoesNotExist() { 1176 val source = base / "source" 1177 val target = base / "target" 1178 assertFailsWith<FileNotFoundException> { 1179 fileSystem.copy(source, target) 1180 } 1181 assertFalse(target in fileSystem.list(base)) 1182 } 1183 1184 @Test 1185 fun copyTargetIsClobbered() { 1186 val source = base / "source" 1187 source.writeUtf8("hello, world!") 1188 val target = base / "target" 1189 target.writeUtf8("this file will be clobbered!") 1190 fileSystem.copy(source, target) 1191 assertTrue(target in fileSystem.list(base)) 1192 assertEquals("hello, world!", target.readUtf8()) 1193 } 1194 1195 @Test 1196 fun deleteFile() { 1197 val path = base / "delete-file" 1198 path.writeUtf8("delete me") 1199 fileSystem.delete(path) 1200 assertTrue(path !in fileSystem.list(base)) 1201 } 1202 1203 @Test 1204 fun deleteFileMustExist() { 1205 val path = base / "delete-file" 1206 path.writeUtf8("delete me") 1207 fileSystem.delete(path, mustExist = true) 1208 assertTrue(path !in fileSystem.list(base)) 1209 } 1210 1211 @Test 1212 fun deleteEmptyDirectory() { 1213 val path = base / "delete-empty-directory" 1214 fileSystem.createDirectory(path) 1215 fileSystem.delete(path) 1216 assertTrue(path !in fileSystem.list(base)) 1217 } 1218 1219 @Test 1220 fun deleteEmptyDirectoryMustExist() { 1221 val path = base / "delete-empty-directory" 1222 fileSystem.createDirectory(path) 1223 fileSystem.delete(path, mustExist = true) 1224 assertTrue(path !in fileSystem.list(base)) 1225 } 1226 1227 @Test 1228 fun deleteDoesNotExist() { 1229 val path = base / "no-such-file" 1230 fileSystem.delete(path) 1231 } 1232 1233 @Test 1234 fun deleteFailsOnNoSuchFileIfMustExist() { 1235 val path = base / "no-such-file" 1236 assertFailsWith<FileNotFoundException> { 1237 fileSystem.delete(path, mustExist = true) 1238 } 1239 } 1240 1241 @Test 1242 fun deleteFailsOnNonEmptyDirectory() { 1243 val path = base / "non-empty-directory" 1244 fileSystem.createDirectory(path) 1245 (path / "file.txt").writeUtf8("inside directory") 1246 val exception = assertFailsWith<IOException> { 1247 fileSystem.delete(path) 1248 } 1249 assertTrue(exception !is FileNotFoundException) 1250 } 1251 1252 @Test 1253 fun deleteFailsOnNonEmptyDirectoryMustExist() { 1254 val path = base / "non-empty-directory" 1255 fileSystem.createDirectory(path) 1256 (path / "file.txt").writeUtf8("inside directory") 1257 val exception = assertFailsWith<IOException> { 1258 fileSystem.delete(path, mustExist = true) 1259 } 1260 assertTrue(exception !is FileNotFoundException) 1261 } 1262 1263 @Test 1264 fun deleteRecursivelyFile() { 1265 val path = base / "delete-recursively-file" 1266 path.writeUtf8("delete me") 1267 fileSystem.deleteRecursively(path) 1268 assertTrue(path !in fileSystem.list(base)) 1269 } 1270 1271 @Test 1272 fun deleteRecursivelyFileMustExist() { 1273 val path = base / "delete-recursively-file" 1274 path.writeUtf8("delete me") 1275 fileSystem.deleteRecursively(path, mustExist = true) 1276 assertTrue(path !in fileSystem.list(base)) 1277 } 1278 1279 @Test 1280 fun deleteRecursivelyEmptyDirectory() { 1281 val path = base / "delete-recursively-empty-directory" 1282 fileSystem.createDirectory(path) 1283 fileSystem.deleteRecursively(path) 1284 assertTrue(path !in fileSystem.list(base)) 1285 } 1286 1287 @Test 1288 fun deleteRecursivelyEmptyDirectoryMustExist() { 1289 val path = base / "delete-recursively-empty-directory" 1290 fileSystem.createDirectory(path) 1291 fileSystem.deleteRecursively(path, mustExist = true) 1292 assertTrue(path !in fileSystem.list(base)) 1293 } 1294 1295 @Test 1296 fun deleteRecursivelyNoSuchFile() { 1297 val path = base / "no-such-file" 1298 fileSystem.deleteRecursively(path) 1299 } 1300 1301 @Test 1302 fun deleteRecursivelyMustExistFailsOnNoSuchFile() { 1303 val path = base / "no-such-file" 1304 assertFailsWith<IOException> { 1305 fileSystem.deleteRecursively(path, mustExist = true) 1306 } 1307 } 1308 1309 @Test 1310 fun deleteRecursivelyNonEmptyDirectory() { 1311 val path = base / "delete-recursively-non-empty-directory" 1312 fileSystem.createDirectory(path) 1313 (path / "file.txt").writeUtf8("inside directory") 1314 fileSystem.deleteRecursively(path) 1315 assertTrue(path !in fileSystem.list(base)) 1316 assertTrue((path / "file.txt") !in fileSystem.list(base)) 1317 } 1318 1319 @Test 1320 fun deleteRecursivelyNonEmptyDirectoryMustExist() { 1321 val path = base / "delete-recursively-non-empty-directory" 1322 fileSystem.createDirectory(path) 1323 (path / "file.txt").writeUtf8("inside directory") 1324 fileSystem.deleteRecursively(path, mustExist = true) 1325 assertTrue(path !in fileSystem.list(base)) 1326 assertTrue((path / "file.txt") !in fileSystem.list(base)) 1327 } 1328 1329 @Test 1330 fun deleteRecursivelyDeepHierarchy() { 1331 fileSystem.createDirectory(base / "a") 1332 fileSystem.createDirectory(base / "a" / "b") 1333 fileSystem.createDirectory(base / "a" / "b" / "c") 1334 (base / "a" / "b" / "c" / "d.txt").writeUtf8("inside deep hierarchy") 1335 fileSystem.deleteRecursively(base / "a") 1336 assertEquals(listOf(), fileSystem.list(base)) 1337 } 1338 1339 @Test 1340 fun deleteRecursivelyDeepHierarchyMustExist() { 1341 fileSystem.createDirectory(base / "a") 1342 fileSystem.createDirectory(base / "a" / "b") 1343 fileSystem.createDirectory(base / "a" / "b" / "c") 1344 (base / "a" / "b" / "c" / "d.txt").writeUtf8("inside deep hierarchy") 1345 fileSystem.deleteRecursively(base / "a", mustExist = true) 1346 assertEquals(listOf(), fileSystem.list(base)) 1347 } 1348 1349 @Test 1350 fun deleteRecursivelyOnSymlinkToFileDeletesOnlyThatSymlink() { 1351 if (!supportsSymlink()) return 1352 1353 val baseA = base / "a" 1354 val baseB = base / "b" 1355 baseB.writeUtf8("b") 1356 fileSystem.createSymlink(baseA, baseB) 1357 fileSystem.deleteRecursively(baseA) 1358 assertEquals("b", baseB.readUtf8()) 1359 } 1360 1361 @Test 1362 fun deleteRecursivelyOnSymlinkToFileDeletesOnlyThatSymlinkMustExist() { 1363 if (!supportsSymlink()) return 1364 1365 val baseA = base / "a" 1366 val baseB = base / "b" 1367 baseB.writeUtf8("b") 1368 fileSystem.createSymlink(baseA, baseB) 1369 fileSystem.deleteRecursively(baseA, mustExist = true) 1370 assertEquals("b", baseB.readUtf8()) 1371 } 1372 1373 @Test 1374 fun deleteRecursivelyOnSymlinkToDirectoryDeletesOnlyThatSymlink() { 1375 if (!supportsSymlink()) return 1376 1377 val baseA = base / "a" 1378 val baseB = base / "b" 1379 val baseBC = base / "b" / "c" 1380 fileSystem.createDirectory(baseB) 1381 baseBC.writeUtf8("c") 1382 fileSystem.createSymlink(baseA, baseB) 1383 fileSystem.deleteRecursively(baseA) 1384 assertEquals("c", baseBC.readUtf8()) 1385 } 1386 1387 @Test 1388 fun deleteRecursivelyOnSymlinkToDirectoryDeletesOnlyThatSymlinkMustExist() { 1389 if (!supportsSymlink()) return 1390 1391 val baseA = base / "a" 1392 val baseB = base / "b" 1393 val baseBC = base / "b" / "c" 1394 fileSystem.createDirectory(baseB) 1395 baseBC.writeUtf8("c") 1396 fileSystem.createSymlink(baseA, baseB) 1397 fileSystem.deleteRecursively(baseA, mustExist = true) 1398 assertEquals("c", baseBC.readUtf8()) 1399 } 1400 1401 @Test 1402 fun deleteRecursivelyOnSymlinkCycleSucceeds() { 1403 if (!supportsSymlink()) return 1404 1405 val baseA = base / "a" 1406 val baseAB = baseA / "b" 1407 val baseAC = baseA / "c" 1408 1409 baseA.createDirectory() 1410 baseAB.writeUtf8("ab") 1411 fileSystem.createSymlink(baseAC, baseA) 1412 1413 fileSystem.deleteRecursively(base) 1414 assertFalse(fileSystem.exists(base)) 1415 } 1416 1417 @Test 1418 fun deleteRecursivelyOnSymlinkCycleSucceedsMustExist() { 1419 if (!supportsSymlink()) return 1420 1421 val baseA = base / "a" 1422 val baseAB = baseA / "b" 1423 val baseAC = baseA / "c" 1424 1425 baseA.createDirectory() 1426 baseAB.writeUtf8("ab") 1427 fileSystem.createSymlink(baseAC, baseA) 1428 1429 fileSystem.deleteRecursively(base, mustExist = true) 1430 assertFalse(fileSystem.exists(base)) 1431 } 1432 1433 @Test 1434 fun deleteRecursivelyOnSymlinkToEnclosingDirectorySucceeds() { 1435 if (!supportsSymlink()) return 1436 1437 val baseA = base / "a" 1438 fileSystem.createSymlink(baseA, ".".toPath()) 1439 1440 fileSystem.deleteRecursively(baseA) 1441 assertFalse(fileSystem.exists(baseA)) 1442 assertTrue(fileSystem.exists(base)) 1443 } 1444 1445 @Test 1446 fun deleteRecursivelyOnSymlinkToEnclosingDirectorySucceedsMustExist() { 1447 if (!supportsSymlink()) return 1448 1449 val baseA = base / "a" 1450 fileSystem.createSymlink(baseA, ".".toPath()) 1451 1452 fileSystem.deleteRecursively(baseA, mustExist = true) 1453 assertFalse(fileSystem.exists(baseA)) 1454 assertTrue(fileSystem.exists(base)) 1455 } 1456 1457 @Test 1458 fun fileMetadata() { 1459 val minTime = clock.now() 1460 val path = base / "file-metadata" 1461 path.writeUtf8("hello, world!") 1462 val maxTime = clock.now() 1463 1464 val metadata = fileSystem.metadata(path) 1465 assertTrue(metadata.isRegularFile) 1466 assertFalse(metadata.isDirectory) 1467 assertEquals(13, metadata.size) 1468 assertInRange(metadata.createdAt, minTime, maxTime) 1469 assertInRange(metadata.lastModifiedAt, minTime, maxTime) 1470 assertInRange(metadata.lastAccessedAt, minTime, maxTime) 1471 } 1472 1473 @Test 1474 fun directoryMetadata() { 1475 val minTime = clock.now() 1476 val path = base / "directory-metadata" 1477 fileSystem.createDirectory(path) 1478 val maxTime = clock.now() 1479 1480 val metadata = fileSystem.metadata(path) 1481 assertFalse(metadata.isRegularFile) 1482 assertTrue(metadata.isDirectory) 1483 // Note that the size check is omitted; we'd expect null but the JVM returns values like 64. 1484 assertInRange(metadata.createdAt, minTime, maxTime) 1485 assertInRange(metadata.lastModifiedAt, minTime, maxTime) 1486 assertInRange(metadata.lastAccessedAt, minTime, maxTime) 1487 } 1488 1489 @Test 1490 fun fileMetadataWithSpecialCharacterNamedFiles() { 1491 val minTime = clock.now() 1492 val path = base / "超サイヤ人" 1493 path.writeUtf8("カカロットよ!") 1494 val maxTime = clock.now() 1495 1496 val metadata = fileSystem.metadata(path) 1497 assertTrue(metadata.isRegularFile) 1498 assertFalse(metadata.isDirectory) 1499 assertEquals(21, metadata.size) 1500 assertInRange(metadata.createdAt, minTime, maxTime) 1501 assertInRange(metadata.lastModifiedAt, minTime, maxTime) 1502 assertInRange(metadata.lastAccessedAt, minTime, maxTime) 1503 } 1504 1505 @Test 1506 fun directoryMetadataWithSpecialCharacterNamedFiles() { 1507 val minTime = clock.now() 1508 val path = base / "Ἰλιάς" 1509 fileSystem.createDirectory(path) 1510 val maxTime = clock.now() 1511 1512 val metadata = fileSystem.metadata(path) 1513 assertFalse(metadata.isRegularFile) 1514 assertTrue(metadata.isDirectory) 1515 // Note that the size check is omitted; we'd expect null but the JVM returns values like 64. 1516 assertInRange(metadata.createdAt, minTime, maxTime) 1517 assertInRange(metadata.lastModifiedAt, minTime, maxTime) 1518 assertInRange(metadata.lastAccessedAt, minTime, maxTime) 1519 } 1520 1521 @Test 1522 fun absentMetadataOrNull() { 1523 val path = base / "no-such-file" 1524 assertNull(fileSystem.metadataOrNull(path)) 1525 } 1526 1527 @Test 1528 fun absentParentDirectoryMetadataOrNull() { 1529 val path = base / "no-such-directory" / "no-such-file" 1530 assertNull(fileSystem.metadataOrNull(path)) 1531 } 1532 1533 @Test 1534 fun parentDirectoryIsFileMetadataOrNull() { 1535 val parent = base / "regular-file" 1536 val path = parent / "no-such-file" 1537 parent.writeUtf8("just a regular file") 1538 // This returns null on Windows and throws IOException on other platforms. 1539 try { 1540 assertNull(fileSystem.metadataOrNull(path)) 1541 } catch (e: IOException) { 1542 // Also okay. 1543 } 1544 } 1545 1546 @Test 1547 @Ignore 1548 fun inaccessibleMetadata() { 1549 // TODO(swankjesse): configure a test directory in CI that exists, but that this process doesn't 1550 // have permission to read metadata of. Perhaps a file in another user's /home directory? 1551 } 1552 1553 @Test 1554 fun absentMetadata() { 1555 val path = base / "no-such-file" 1556 assertFailsWith<FileNotFoundException> { 1557 fileSystem.metadata(path) 1558 } 1559 } 1560 1561 @Test 1562 fun fileExists() { 1563 val path = base / "file-exists" 1564 assertFalse(fileSystem.exists(path)) 1565 path.writeUtf8("hello, world!") 1566 assertTrue(fileSystem.exists(path)) 1567 } 1568 1569 @Test 1570 fun directoryExists() { 1571 val path = base / "directory-exists" 1572 assertFalse(fileSystem.exists(path)) 1573 fileSystem.createDirectory(path) 1574 assertTrue(fileSystem.exists(path)) 1575 } 1576 1577 @Test 1578 fun deleteOpenForWritingFailsOnWindows() { 1579 val file = base / "file.txt" 1580 expectIOExceptionOnWindows(exceptJs = true) { 1581 fileSystem.sink(file).use { 1582 fileSystem.delete(file) 1583 } 1584 } 1585 } 1586 1587 @Test 1588 fun deleteOpenForReadingFailsOnWindows() { 1589 val file = base / "file.txt" 1590 file.writeUtf8("abc") 1591 expectIOExceptionOnWindows(exceptJs = true) { 1592 fileSystem.source(file).use { 1593 fileSystem.delete(file) 1594 } 1595 } 1596 } 1597 1598 @Test 1599 fun renameSourceIsOpenFailsOnWindows() { 1600 val from = base / "from.txt" 1601 val to = base / "to.txt" 1602 from.writeUtf8("source file") 1603 to.writeUtf8("target file") 1604 expectIOExceptionOnWindows(exceptJs = true) { 1605 fileSystem.source(from).use { 1606 fileSystem.atomicMove(from, to) 1607 } 1608 } 1609 } 1610 1611 @Test 1612 fun renameTargetIsOpenFailsOnWindows() { 1613 val from = base / "from.txt" 1614 val to = base / "to.txt" 1615 from.writeUtf8("source file") 1616 to.writeUtf8("target file") 1617 1618 val expectCrash = !allowRenameWhenTargetIsOpen 1619 try { 1620 fileSystem.source(to).use { 1621 fileSystem.atomicMove(from, to) 1622 } 1623 assertFalse(expectCrash) 1624 } catch (_: IOException) { 1625 assertTrue(expectCrash) 1626 } 1627 } 1628 1629 @Test 1630 fun deleteContentsOfParentOfFileOpenForReadingFailsOnWindows() { 1631 val parentA = (base / "a") 1632 fileSystem.createDirectory(parentA) 1633 val parentAB = parentA / "b" 1634 fileSystem.createDirectory(parentAB) 1635 val parentABC = parentAB / "c" 1636 fileSystem.createDirectory(parentABC) 1637 val file = parentABC / "file.txt" 1638 file.writeUtf8("child file") 1639 expectIOExceptionOnWindows { 1640 fileSystem.source(file).use { 1641 fileSystem.delete(file) 1642 fileSystem.delete(parentABC) 1643 fileSystem.delete(parentAB) 1644 fileSystem.delete(parentA) 1645 } 1646 } 1647 } 1648 1649 @Test fun fileHandleWriteAndRead() { 1650 val path = base / "file-handle-write-and-read" 1651 fileSystem.openReadWrite(path).use { handle -> 1652 1653 handle.sink().buffer().use { sink -> 1654 sink.writeUtf8("abcdefghijklmnop") 1655 } 1656 1657 handle.source().buffer().use { source -> 1658 assertEquals("abcde", source.readUtf8(5)) 1659 assertEquals("fghijklmnop", source.readUtf8()) 1660 } 1661 } 1662 } 1663 1664 @Test fun fileHandleWriteAndOverwrite() { 1665 val path = base / "file-handle-write-and-overwrite" 1666 fileSystem.openReadWrite(path).use { handle -> 1667 1668 handle.sink().buffer().use { sink -> 1669 sink.writeUtf8("abcdefghij") 1670 } 1671 1672 handle.sink(fileOffset = handle.size() - 3).buffer().use { sink -> 1673 sink.writeUtf8("HIJKLMNOP") 1674 } 1675 1676 handle.source().buffer().use { source -> 1677 assertEquals("abcdefgHIJKLMNOP", source.readUtf8()) 1678 } 1679 } 1680 } 1681 1682 @Test fun fileHandleWriteBeyondEnd() { 1683 val path = base / "file-handle-write-beyond-end" 1684 fileSystem.openReadWrite(path).use { handle -> 1685 1686 handle.sink(fileOffset = 10).buffer().use { sink -> 1687 sink.writeUtf8("klmnop") 1688 } 1689 1690 handle.source().buffer().use { source -> 1691 assertEquals("00000000000000000000", source.readByteString(10).hex()) 1692 assertEquals("klmnop", source.readUtf8()) 1693 } 1694 } 1695 } 1696 1697 @Test fun fileHandleResizeSmaller() { 1698 val path = base / "file-handle-resize-smaller" 1699 fileSystem.openReadWrite(path).use { handle -> 1700 1701 handle.sink().buffer().use { sink -> 1702 sink.writeUtf8("abcdefghijklmnop") 1703 } 1704 1705 handle.resize(10) 1706 1707 handle.source().buffer().use { source -> 1708 assertEquals("abcdefghij", source.readUtf8()) 1709 } 1710 } 1711 } 1712 1713 @Test fun fileHandleResizeLarger() { 1714 val path = base / "file-handle-resize-larger" 1715 fileSystem.openReadWrite(path).use { handle -> 1716 1717 handle.sink().buffer().use { sink -> 1718 sink.writeUtf8("abcde") 1719 } 1720 1721 handle.resize(15) 1722 1723 handle.source().buffer().use { source -> 1724 assertEquals("abcde", source.readUtf8(5)) 1725 assertEquals("00000000000000000000", source.readByteString().hex()) 1726 } 1727 } 1728 } 1729 1730 @Test fun fileHandleFlush() { 1731 if (windowsLimitations) return // Open for reading and writing simultaneously. 1732 1733 val path = base / "file-handle-flush" 1734 fileSystem.openReadWrite(path).use { handleA -> 1735 handleA.sink().buffer().use { sink -> 1736 sink.writeUtf8("abcde") 1737 } 1738 handleA.flush() 1739 1740 fileSystem.openReadWrite(path).use { handleB -> 1741 handleB.source().buffer().use { source -> 1742 assertEquals("abcde", source.readUtf8()) 1743 } 1744 } 1745 } 1746 } 1747 1748 @Test fun fileHandleLargeBufferedWriteAndRead() { 1749 if (isBrowser()) return // This test errors on browsers in CI. 1750 1751 val data = randomBytes(1024 * 1024 * 8) 1752 1753 val path = base / "file-handle-large-buffered-write-and-read" 1754 fileSystem.openReadWrite(path).use { handle -> 1755 handle.sink().buffer().use { sink -> 1756 sink.write(data) 1757 } 1758 } 1759 1760 fileSystem.openReadWrite(path).use { handle -> 1761 handle.source().buffer().use { source -> 1762 assertEquals(data, source.readByteString()) 1763 } 1764 } 1765 } 1766 1767 @Test fun fileHandleLargeArrayWriteAndRead() { 1768 if (isBrowser()) return // This test errors on browsers in CI. 1769 1770 val path = base / "file-handle-large-array-write-and-read" 1771 1772 val writtenBytes = randomBytes(1024 * 1024 * 8) 1773 fileSystem.openReadWrite(path).use { handle -> 1774 handle.write(0, writtenBytes.toByteArray(), 0, writtenBytes.size) 1775 } 1776 1777 val readBytes = fileSystem.openReadWrite(path).use { handle -> 1778 val byteArray = ByteArray(writtenBytes.size) 1779 handle.read(0, byteArray, 0, byteArray.size) 1780 return@use byteArray.toByteString(0, byteArray.size) // Parameters necessary for issue 910. 1781 } 1782 1783 assertEquals(writtenBytes, readBytes) 1784 } 1785 1786 @Test fun fileHandleEmptyArrayWriteAndRead() { 1787 val path = base / "file-handle-empty-array-write-and-read" 1788 1789 val writtenBytes = ByteArray(0) 1790 fileSystem.openReadWrite(path).use { handle -> 1791 handle.write(0, writtenBytes, 0, writtenBytes.size) 1792 } 1793 1794 val readBytes = fileSystem.openReadWrite(path).use { handle -> 1795 val byteArray = ByteArray(writtenBytes.size) 1796 handle.read(0, byteArray, 0, byteArray.size) 1797 return@use byteArray 1798 } 1799 1800 assertContentEquals(writtenBytes, readBytes) 1801 } 1802 1803 @Test fun fileHandleSinkPosition() { 1804 val path = base / "file-handle-sink-position" 1805 1806 fileSystem.openReadWrite(path).use { handle -> 1807 handle.sink().use { sink -> 1808 sink.write(Buffer().writeUtf8("abcde"), 5) 1809 assertEquals(5, handle.position(sink)) 1810 sink.write(Buffer().writeUtf8("fghijklmno"), 10) 1811 assertEquals(15, handle.position(sink)) 1812 } 1813 1814 handle.sink(200).use { sink -> 1815 sink.write(Buffer().writeUtf8("abcde"), 5) 1816 assertEquals(205, handle.position(sink)) 1817 sink.write(Buffer().writeUtf8("fghijklmno"), 10) 1818 assertEquals(215, handle.position(sink)) 1819 } 1820 } 1821 } 1822 1823 @Test fun fileHandleBufferedSinkPosition() { 1824 val path = base / "file-handle-buffered-sink-position" 1825 1826 fileSystem.openReadWrite(path).use { handle -> 1827 handle.sink().buffer().use { sink -> 1828 sink.writeUtf8("abcde") 1829 assertEquals(5, handle.position(sink)) 1830 sink.writeUtf8("fghijklmno") 1831 assertEquals(15, handle.position(sink)) 1832 } 1833 1834 handle.sink(200).buffer().use { sink -> 1835 sink.writeUtf8("abcde") 1836 assertEquals(205, handle.position(sink)) 1837 sink.writeUtf8("fghijklmno") 1838 assertEquals(215, handle.position(sink)) 1839 } 1840 } 1841 } 1842 1843 @Test fun fileHandleSinkReposition() { 1844 val path = base / "file-handle-sink-reposition" 1845 1846 fileSystem.openReadWrite(path).use { handle -> 1847 handle.sink().use { sink -> 1848 sink.write(Buffer().writeUtf8("abcdefghij"), 10) 1849 handle.reposition(sink, 5) 1850 assertEquals(5, handle.position(sink)) 1851 sink.write(Buffer().writeUtf8("KLM"), 3) 1852 assertEquals(8, handle.position(sink)) 1853 1854 handle.reposition(sink, 200) 1855 sink.write(Buffer().writeUtf8("ABCDEFGHIJ"), 10) 1856 handle.reposition(sink, 205) 1857 assertEquals(205, handle.position(sink)) 1858 sink.write(Buffer().writeUtf8("klm"), 3) 1859 assertEquals(208, handle.position(sink)) 1860 } 1861 1862 Buffer().also { 1863 handle.read(fileOffset = 0, sink = it, byteCount = 10) 1864 assertEquals("abcdeKLMij", it.readUtf8()) 1865 } 1866 1867 Buffer().also { 1868 handle.read(fileOffset = 200, sink = it, byteCount = 15) 1869 assertEquals("ABCDEklmIJ", it.readUtf8()) 1870 } 1871 } 1872 } 1873 1874 @Test fun fileHandleBufferedSinkReposition() { 1875 val path = base / "file-handle-buffered-sink-reposition" 1876 1877 fileSystem.openReadWrite(path).use { handle -> 1878 handle.sink().buffer().use { sink -> 1879 sink.write(Buffer().writeUtf8("abcdefghij"), 10) 1880 handle.reposition(sink, 5) 1881 assertEquals(5, handle.position(sink)) 1882 sink.write(Buffer().writeUtf8("KLM"), 3) 1883 assertEquals(8, handle.position(sink)) 1884 1885 handle.reposition(sink, 200) 1886 sink.write(Buffer().writeUtf8("ABCDEFGHIJ"), 10) 1887 handle.reposition(sink, 205) 1888 assertEquals(205, handle.position(sink)) 1889 sink.write(Buffer().writeUtf8("klm"), 3) 1890 assertEquals(208, handle.position(sink)) 1891 } 1892 1893 Buffer().also { 1894 handle.read(fileOffset = 0, sink = it, byteCount = 10) 1895 assertEquals("abcdeKLMij", it.readUtf8()) 1896 } 1897 1898 Buffer().also { 1899 handle.read(fileOffset = 200, sink = it, byteCount = 15) 1900 assertEquals("ABCDEklmIJ", it.readUtf8()) 1901 } 1902 } 1903 } 1904 1905 @Test fun fileHandleSourceHappyPath() { 1906 val path = base / "file-handle-source" 1907 fileSystem.write(path) { 1908 writeUtf8("abcdefghijklmnop") 1909 } 1910 1911 fileSystem.openReadOnly(path).use { handle -> 1912 assertEquals(16L, handle.size()) 1913 val buffer = Buffer() 1914 1915 handle.source().use { source -> 1916 assertEquals(0L, handle.position(source)) 1917 assertEquals(4L, source.read(buffer, 4L)) 1918 assertEquals("abcd", buffer.readUtf8()) 1919 assertEquals(4L, handle.position(source)) 1920 } 1921 1922 handle.source(fileOffset = 8L).use { source -> 1923 assertEquals(8L, handle.position(source)) 1924 assertEquals(4L, source.read(buffer, 4L)) 1925 assertEquals("ijkl", buffer.readUtf8()) 1926 assertEquals(12L, handle.position(source)) 1927 } 1928 1929 handle.source(fileOffset = 16L).use { source -> 1930 assertEquals(16L, handle.position(source)) 1931 assertEquals(-1L, source.read(buffer, 4L)) 1932 assertEquals("", buffer.readUtf8()) 1933 assertEquals(16L, handle.position(source)) 1934 } 1935 } 1936 } 1937 1938 @Test fun fileHandleSourceReposition() { 1939 val path = base / "file-handle-source-reposition" 1940 fileSystem.write(path) { 1941 writeUtf8("abcdefghijklmnop") 1942 } 1943 1944 fileSystem.openReadOnly(path).use { handle -> 1945 assertEquals(16L, handle.size()) 1946 val buffer = Buffer() 1947 1948 handle.source().use { source -> 1949 handle.reposition(source, 12L) 1950 assertEquals(12L, handle.position(source)) 1951 assertEquals(4L, source.read(buffer, 4L)) 1952 assertEquals("mnop", buffer.readUtf8()) 1953 assertEquals(-1L, source.read(buffer, 4L)) 1954 assertEquals("", buffer.readUtf8()) 1955 assertEquals(16L, handle.position(source)) 1956 1957 handle.reposition(source, 0L) 1958 assertEquals(0L, handle.position(source)) 1959 assertEquals(4L, source.read(buffer, 4L)) 1960 assertEquals("abcd", buffer.readUtf8()) 1961 assertEquals(4L, handle.position(source)) 1962 1963 handle.reposition(source, 8L) 1964 assertEquals(8L, handle.position(source)) 1965 assertEquals(4L, source.read(buffer, 4L)) 1966 assertEquals("ijkl", buffer.readUtf8()) 1967 assertEquals(12L, handle.position(source)) 1968 1969 handle.reposition(source, 16L) 1970 assertEquals(16L, handle.position(source)) 1971 assertEquals(-1L, source.read(buffer, 4L)) 1972 assertEquals("", buffer.readUtf8()) 1973 assertEquals(16L, handle.position(source)) 1974 } 1975 } 1976 } 1977 1978 @Test fun fileHandleBufferedSourceReposition() { 1979 val path = base / "file-handle-buffered-source-reposition" 1980 fileSystem.write(path) { 1981 writeUtf8("abcdefghijklmnop") 1982 } 1983 1984 fileSystem.openReadOnly(path).use { handle -> 1985 assertEquals(16L, handle.size()) 1986 val buffer = Buffer() 1987 1988 handle.source().buffer().use { source -> 1989 handle.reposition(source, 12L) 1990 assertEquals(0L, source.buffer.size) 1991 assertEquals(12L, handle.position(source)) 1992 assertEquals(4L, source.read(buffer, 4L)) 1993 assertEquals(0L, source.buffer.size) 1994 assertEquals("mnop", buffer.readUtf8()) 1995 assertEquals(-1L, source.read(buffer, 4L)) 1996 assertEquals("", buffer.readUtf8()) 1997 assertEquals(16L, handle.position(source)) 1998 1999 handle.reposition(source, 0L) 2000 assertEquals(0L, source.buffer.size) 2001 assertEquals(0L, handle.position(source)) 2002 assertEquals(4L, source.read(buffer, 4L)) 2003 assertEquals(12L, source.buffer.size) // Buffered bytes accumulated. 2004 assertEquals("abcd", buffer.readUtf8()) 2005 assertEquals(4L, handle.position(source)) 2006 2007 handle.reposition(source, 8L) 2008 assertEquals(8L, source.buffer.size) // Buffered bytes preserved. 2009 assertEquals(8L, handle.position(source)) 2010 assertEquals(4L, source.read(buffer, 4L)) 2011 assertEquals(4L, source.buffer.size) 2012 assertEquals("ijkl", buffer.readUtf8()) 2013 assertEquals(12L, handle.position(source)) 2014 2015 handle.reposition(source, 16L) 2016 assertEquals(0L, source.buffer.size) 2017 assertEquals(16L, handle.position(source)) 2018 assertEquals(-1L, source.read(buffer, 4L)) 2019 assertEquals(0L, source.buffer.size) 2020 assertEquals("", buffer.readUtf8()) 2021 assertEquals(16L, handle.position(source)) 2022 } 2023 } 2024 } 2025 2026 @Test fun fileHandleSourceSeekBackwards() { 2027 val path = base / "file-handle-source-backwards" 2028 fileSystem.write(path) { 2029 writeUtf8("abcdefghijklmnop") 2030 } 2031 fileSystem.openReadOnly(path).use { handle -> 2032 assertEquals(16L, handle.size()) 2033 val buffer = Buffer() 2034 2035 handle.source().use { source -> 2036 assertEquals(0L, handle.position(source)) 2037 assertEquals(16L, source.read(buffer, 16L)) 2038 assertEquals("abcdefghijklmnop", buffer.readUtf8()) 2039 assertEquals(16L, handle.position(source)) 2040 } 2041 2042 handle.source(0L).use { source -> 2043 assertEquals(0L, handle.position(source)) 2044 assertEquals(16L, source.read(buffer, 16L)) 2045 assertEquals("abcdefghijklmnop", buffer.readUtf8()) 2046 assertEquals(16L, handle.position(source)) 2047 } 2048 } 2049 } 2050 2051 @Test fun bufferedFileHandleSourceHappyPath() { 2052 val path = base / "file-handle-source" 2053 fileSystem.write(path) { 2054 writeUtf8("abcdefghijklmnop") 2055 } 2056 2057 fileSystem.openReadOnly(path).use { handle -> 2058 assertEquals(16L, handle.size()) 2059 val buffer = Buffer() 2060 2061 handle.source().buffer().use { source -> 2062 assertEquals(0L, handle.position(source)) 2063 assertEquals(4L, source.read(buffer, 4L)) 2064 assertEquals("abcd", buffer.readUtf8()) 2065 assertEquals(4L, handle.position(source)) 2066 } 2067 2068 handle.source(fileOffset = 8L).buffer().use { source -> 2069 assertEquals(8L, handle.position(source)) 2070 assertEquals(4L, source.read(buffer, 4L)) 2071 assertEquals("ijkl", buffer.readUtf8()) 2072 assertEquals(12L, handle.position(source)) 2073 } 2074 2075 handle.source(fileOffset = 16L).buffer().use { source -> 2076 assertEquals(16L, handle.position(source)) 2077 assertEquals(-1L, source.read(buffer, 4L)) 2078 assertEquals("", buffer.readUtf8()) 2079 assertEquals(16L, handle.position(source)) 2080 } 2081 } 2082 } 2083 2084 @Test fun bufferedFileHandleSourceSeekBackwards() { 2085 val path = base / "file-handle-source-backwards" 2086 fileSystem.write(path) { 2087 writeUtf8("abcdefghijklmnop") 2088 } 2089 fileSystem.openReadOnly(path).use { handle -> 2090 assertEquals(16L, handle.size()) 2091 val buffer = Buffer() 2092 2093 handle.source().buffer().use { source -> 2094 assertEquals(0L, handle.position(source)) 2095 assertEquals(16L, source.read(buffer, 16L)) 2096 assertEquals("abcdefghijklmnop", buffer.readUtf8()) 2097 assertEquals(16L, handle.position(source)) 2098 } 2099 2100 handle.source(0L).buffer().use { source -> 2101 assertEquals(0L, handle.position(source)) 2102 assertEquals(16L, source.read(buffer, 16L)) 2103 assertEquals("abcdefghijklmnop", buffer.readUtf8()) 2104 assertEquals(16L, handle.position(source)) 2105 } 2106 } 2107 } 2108 2109 @Test fun openReadOnlyThrowsOnAttemptToWrite() { 2110 val path = base / "file-handle-source" 2111 fileSystem.write(path) { 2112 writeUtf8("abcdefghijklmnop") 2113 } 2114 2115 fileSystem.openReadOnly(path).use { handle -> 2116 try { 2117 handle.sink() 2118 fail() 2119 } catch (_: IllegalStateException) { 2120 } 2121 2122 try { 2123 handle.flush() 2124 fail() 2125 } catch (_: IllegalStateException) { 2126 } 2127 2128 try { 2129 handle.resize(0L) 2130 fail() 2131 } catch (_: IllegalStateException) { 2132 } 2133 2134 try { 2135 handle.write(0L, Buffer().writeUtf8("hello"), 5L) 2136 fail() 2137 } catch (_: IllegalStateException) { 2138 } 2139 } 2140 } 2141 2142 @Test fun openReadOnlyFailsOnAbsentFile() { 2143 val path = base / "file-handle-source" 2144 2145 try { 2146 fileSystem.openReadOnly(path) 2147 fail() 2148 } catch (_: IOException) { 2149 } 2150 } 2151 2152 @Test fun openReadWriteCreatesAbsentFile() { 2153 val path = base / "file-handle-source" 2154 2155 fileSystem.openReadWrite(path).use { 2156 } 2157 2158 assertEquals("", path.readUtf8()) 2159 } 2160 2161 @Test fun openReadWriteCreatesAbsentFileMustCreate() { 2162 val path = base / "file-handle-source" 2163 2164 fileSystem.openReadWrite(path, mustCreate = true).use { 2165 } 2166 2167 assertEquals("", path.readUtf8()) 2168 } 2169 2170 @Test fun openReadWriteMustCreateThrowsIfAlreadyExists() { 2171 val path = base / "file-handle-source" 2172 path.writeUtf8("First!") 2173 2174 assertFailsWith<IOException> { 2175 fileSystem.openReadWrite(path, mustCreate = true).use {} 2176 } 2177 } 2178 2179 @Test fun openReadWriteMustExist() { 2180 val path = base / "file-handle-source" 2181 path.writeUtf8("one") 2182 2183 fileSystem.openReadWrite(path, mustExist = true).use { handle -> 2184 handle.write(3L, Buffer().writeUtf8(" two"), 4L) 2185 } 2186 2187 assertEquals("one two", path.readUtf8()) 2188 } 2189 2190 @Test fun openReadWriteMustExistThrowsIfAbsent() { 2191 val path = base / "file-handle-source" 2192 2193 assertFailsWith<IOException> { 2194 fileSystem.openReadWrite(path, mustExist = true).use {} 2195 } 2196 } 2197 2198 @Test fun openReadWriteThrowsIfBothMustCreateAndMustExist() { 2199 val path = base / "file-handle-source" 2200 2201 assertFailsWith<IllegalArgumentException> { 2202 fileSystem.openReadWrite(path, mustCreate = true, mustExist = true).use {} 2203 } 2204 } 2205 2206 @Test fun sinkPositionFailsAfterClose() { 2207 val path = base / "sink-position-fails-after-close" 2208 2209 fileSystem.openReadWrite(path).use { handle -> 2210 val sink = handle.sink() 2211 sink.close() 2212 try { 2213 handle.position(sink) 2214 fail() 2215 } catch (_: IllegalStateException) { 2216 } 2217 try { 2218 handle.position(sink.buffer()) 2219 fail() 2220 } catch (_: IllegalStateException) { 2221 } 2222 } 2223 } 2224 2225 @Test fun sinkRepositionFailsAfterClose() { 2226 val path = base / "sink-reposition-fails-after-close" 2227 2228 fileSystem.openReadWrite(path).use { handle -> 2229 val sink = handle.sink() 2230 sink.close() 2231 try { 2232 handle.reposition(sink, 1L) 2233 fail() 2234 } catch (_: IllegalStateException) { 2235 } 2236 try { 2237 handle.reposition(sink.buffer(), 1L) 2238 fail() 2239 } catch (_: IllegalStateException) { 2240 } 2241 } 2242 } 2243 2244 @Test fun sourcePositionFailsAfterClose() { 2245 val path = base / "source-position-fails-after-close" 2246 2247 fileSystem.openReadWrite(path).use { handle -> 2248 val source = handle.source() 2249 source.close() 2250 try { 2251 handle.position(source) 2252 fail() 2253 } catch (_: IllegalStateException) { 2254 } 2255 try { 2256 handle.position(source.buffer()) 2257 fail() 2258 } catch (_: IllegalStateException) { 2259 } 2260 } 2261 } 2262 2263 @Test fun sourceRepositionFailsAfterClose() { 2264 val path = base / "source-reposition-fails-after-close" 2265 2266 fileSystem.openReadWrite(path).use { handle -> 2267 val source = handle.source() 2268 source.close() 2269 try { 2270 handle.reposition(source, 1L) 2271 fail() 2272 } catch (_: IllegalStateException) { 2273 } 2274 try { 2275 handle.reposition(source.buffer(), 1L) 2276 fail() 2277 } catch (_: IllegalStateException) { 2278 } 2279 } 2280 } 2281 2282 @Test fun sizeFailsAfterClose() { 2283 val path = base / "size-fails-after-close" 2284 2285 val handle = fileSystem.openReadWrite(path) 2286 handle.close() 2287 try { 2288 handle.size() 2289 fail() 2290 } catch (_: IllegalStateException) { 2291 } 2292 } 2293 2294 @Test 2295 fun absoluteSymlinkMetadata() { 2296 if (!supportsSymlink()) return 2297 2298 val target = base / "symlink-target" 2299 val source = base / "symlink-source" 2300 2301 val minTime = clock.now() 2302 fileSystem.createSymlink(source, target) 2303 val maxTime = clock.now() 2304 2305 val sourceMetadata = fileSystem.metadata(source) 2306 // Okio's WasiFileSystem only creates relative symlinks. 2307 assertEquals( 2308 when { 2309 isWasiFileSystem -> target.relativeTo(source.parent!!) 2310 else -> target 2311 }, 2312 sourceMetadata.symlinkTarget, 2313 ) 2314 assertInRange(sourceMetadata.createdAt, minTime, maxTime) 2315 } 2316 2317 @Test 2318 fun relativeSymlinkMetadata() { 2319 if (!supportsSymlink()) return 2320 2321 val target = "symlink-target".toPath() 2322 val source = base / "symlink-source" 2323 2324 val minTime = clock.now() 2325 fileSystem.createSymlink(source, target) 2326 val maxTime = clock.now() 2327 2328 val sourceMetadata = fileSystem.metadata(source) 2329 assertEquals(target, sourceMetadata.symlinkTarget) 2330 assertInRange(sourceMetadata.createdAt, minTime, maxTime) 2331 } 2332 2333 @Test 2334 fun createSymlinkSourceAlreadyExists() { 2335 if (!supportsSymlink()) return 2336 2337 val target = base / "symlink-target" 2338 val source = base / "symlink-source" 2339 source.writeUtf8("hello") 2340 val exception = assertFailsWith<IOException> { 2341 fileSystem.createSymlink(source, target) 2342 } 2343 assertTrue(exception !is FileNotFoundException) 2344 } 2345 2346 @Test 2347 fun createSymlinkParentDirectoryDoesNotExist() { 2348 if (!supportsSymlink()) return 2349 2350 val source = base / "no-such-directory" / "source" 2351 val target = base / "target" 2352 val e = assertFailsWith<IOException> { 2353 fileSystem.createSymlink(source, target) 2354 } 2355 assertTrue(e !is FileNotFoundException) 2356 } 2357 2358 @Test 2359 fun openSymlinkSource() { 2360 if (!supportsSymlink()) return 2361 2362 val target = base / "symlink-target" 2363 val source = base / "symlink-source" 2364 fileSystem.createSymlink(source, target) 2365 target.writeUtf8("I am the target file") 2366 val sourceContent = fileSystem.source(source).buffer().use { it.readUtf8() } 2367 assertEquals("I am the target file", sourceContent) 2368 } 2369 2370 @Test 2371 fun openSymlinkSink() { 2372 if (!supportsSymlink()) return 2373 if (isJimFileSystem()) return 2374 2375 val target = base / "symlink-target" 2376 val source = base / "symlink-source" 2377 fileSystem.createSymlink(source, target) 2378 fileSystem.sink(source).buffer().use { 2379 it.writeUtf8("This writes to the the source file") 2380 } 2381 assertEquals("This writes to the the source file", target.readUtf8()) 2382 } 2383 2384 @Test 2385 fun openFileWithDirectorySymlink() { 2386 if (!supportsSymlink()) return 2387 2388 val baseA = base / "a" 2389 val baseAA = base / "a" / "a" 2390 val baseB = base / "b" 2391 val baseBA = base / "b" / "a" 2392 fileSystem.createDirectory(baseA) 2393 baseAA.writeUtf8("aa") 2394 fileSystem.createSymlink(baseB, baseA) 2395 assertEquals("aa", baseAA.readUtf8()) 2396 assertEquals("aa", baseBA.readUtf8()) 2397 } 2398 2399 @Test 2400 fun openSymlinkFileHandle() { 2401 if (!supportsSymlink()) return 2402 2403 val target = base / "symlink-target" 2404 val source = base / "symlink-source" 2405 fileSystem.createSymlink(source, target) 2406 target.writeUtf8("I am the target file") 2407 val sourceContent = fileSystem.openReadOnly(source).use { fileHandle -> 2408 fileHandle.source().buffer().use { it.readUtf8() } 2409 } 2410 assertEquals("I am the target file", sourceContent) 2411 } 2412 2413 @Test 2414 fun listSymlinkDirectory() { 2415 if (!supportsSymlink()) return 2416 2417 val baseA = base / "a" 2418 val baseAA = base / "a" / "a" 2419 val baseAB = base / "a" / "b" 2420 val baseB = base / "b" 2421 val baseBA = base / "b" / "a" 2422 val baseBB = base / "b" / "b" 2423 fileSystem.createDirectory(baseA) 2424 baseAA.writeUtf8("aa") 2425 baseAB.writeUtf8("ab") 2426 fileSystem.createSymlink(baseB, baseA) 2427 assertEquals(listOf(baseBA, baseBB), fileSystem.list(baseB)) 2428 } 2429 2430 @Test 2431 fun symlinkFileLastAccessedAt() { 2432 if (!supportsSymlink()) return 2433 2434 val target = base / "symlink-target" 2435 val source = base / "symlink-source" 2436 target.writeUtf8("a") 2437 fileSystem.createSymlink(source, target) 2438 tryAdvanceTime() 2439 val minTime = clock.now() 2440 assertEquals("a", source.readUtf8()) 2441 val maxTime = clock.now() 2442 assertInRange(fileSystem.metadata(source).lastAccessedAt, minTime, maxTime) 2443 } 2444 2445 @Test 2446 fun symlinkDirectoryLastAccessedAt() { 2447 if (!supportsSymlink()) return 2448 2449 val baseA = base / "a" 2450 val baseAA = base / "a" / "a" 2451 val baseB = base / "b" 2452 val baseBA = base / "b" / "a" 2453 fileSystem.createDirectory(baseA) 2454 baseAA.writeUtf8("aa") 2455 fileSystem.createSymlink(baseB, baseA) 2456 tryAdvanceTime() 2457 val minTime = clock.now() 2458 assertEquals("aa", baseBA.readUtf8()) 2459 val maxTime = clock.now() 2460 assertInRange(fileSystem.metadata(baseB).lastAccessedAt, minTime, maxTime) 2461 } 2462 2463 @Test 2464 fun deleteSymlinkDoesntDeleteTargetFile() { 2465 if (!supportsSymlink()) return 2466 2467 val target = base / "symlink-target" 2468 val source = base / "symlink-source" 2469 target.writeUtf8("I am the target file") 2470 fileSystem.createSymlink(source, target) 2471 fileSystem.delete(source) 2472 assertEquals("I am the target file", target.readUtf8()) 2473 } 2474 2475 @Test 2476 fun moveSymlinkDoesntMoveTargetFile() { 2477 if (!supportsSymlink()) return 2478 2479 val target = base / "symlink-target" 2480 val source1 = base / "symlink-source-1" 2481 val source2 = base / "symlink-source-2" 2482 target.writeUtf8("I am the target file") 2483 fileSystem.createSymlink(source1, target) 2484 fileSystem.atomicMove(source1, source2) 2485 assertEquals("I am the target file", target.readUtf8()) 2486 assertEquals("I am the target file", source2.readUtf8()) 2487 // Okio's WasiFileSystem only creates relative symlinks. 2488 assertEquals( 2489 when { 2490 isWasiFileSystem -> target.relativeTo(source1.parent!!) 2491 else -> target 2492 }, 2493 fileSystem.metadata(source2).symlinkTarget, 2494 ) 2495 } 2496 2497 @Test 2498 fun symlinkCanBeRelative() { 2499 if (!supportsSymlink()) return 2500 2501 val relativeTarget = "symlink-target".toPath() 2502 val absoluteTarget = base / relativeTarget 2503 val source = base / "symlink-source" 2504 absoluteTarget.writeUtf8("I am the target file") 2505 fileSystem.createSymlink(source, relativeTarget) 2506 assertEquals("I am the target file", source.readUtf8()) 2507 } 2508 2509 @Test 2510 fun symlinkCanBeRelativeWithDotDots() { 2511 if (!supportsSymlink()) return 2512 2513 val relativeTarget = "../b/symlink-target".toPath() 2514 val absoluteTarget = base / "b" / "symlink-target" 2515 val absoluteSource = base / "a" / "symlink-source" 2516 fileSystem.createDirectory(absoluteSource.parent!!) 2517 fileSystem.createDirectory(absoluteTarget.parent!!) 2518 absoluteTarget.writeUtf8("I am the target file") 2519 fileSystem.createSymlink(absoluteSource, relativeTarget) 2520 assertEquals("I am the target file", absoluteSource.readUtf8()) 2521 } 2522 2523 @Test 2524 fun followingRecursiveSymlinksIsOkay() { 2525 if (!supportsSymlink()) return 2526 2527 val pathA = base / "symlink-a" 2528 val pathB = base / "symlink-b" 2529 val pathC = base / "symlink-c" 2530 val target = base / "symlink-target" 2531 fileSystem.createSymlink(pathA, pathB) 2532 fileSystem.createSymlink(pathB, pathC) 2533 fileSystem.createSymlink(pathC, target) 2534 target.writeUtf8("I am the target file") 2535 assertEquals("I am the target file", pathC.readUtf8()) 2536 assertEquals("I am the target file", pathB.readUtf8()) 2537 assertEquals("I am the target file", pathA.readUtf8()) 2538 } 2539 2540 @Test 2541 fun symlinkCycle() { 2542 if (!supportsSymlink()) return 2543 2544 val pathA = base / "symlink-a" 2545 val pathB = base / "symlink-b" 2546 fileSystem.createSymlink(pathA, pathB) 2547 fileSystem.createSymlink(pathB, pathA) 2548 assertFailsWith<IOException> { 2549 pathB.writeUtf8("This should not work") 2550 } 2551 assertFailsWith<IOException> { 2552 pathB.readUtf8() 2553 } 2554 } 2555 2556 protected fun supportsSymlink(): Boolean { 2557 if (fileSystem.isFakeFileSystem) return fileSystem.allowSymlinks 2558 if (windowsLimitations) return false 2559 return when (fileSystem::class.simpleName) { 2560 "JvmSystemFileSystem", 2561 -> false 2562 else -> true 2563 } 2564 } 2565 2566 private fun expectIOExceptionOnWindows( 2567 exceptJs: Boolean = false, 2568 block: () -> Unit, 2569 ) { 2570 val expectCrash = windowsLimitations && (!isNodeJsFileSystem || !exceptJs) 2571 try { 2572 block() 2573 assertFalse(expectCrash) 2574 } catch (_: IOException) { 2575 assertTrue(expectCrash) 2576 } 2577 } 2578 2579 fun Path.readUtf8(): String { 2580 return fileSystem.source(this).buffer().use { 2581 it.readUtf8() 2582 } 2583 } 2584 2585 fun Path.writeUtf8(string: String) { 2586 fileSystem.sink(this).buffer().use { 2587 it.writeUtf8(string) 2588 } 2589 } 2590 2591 fun Path.createDirectory() { 2592 fileSystem.createDirectory(this) 2593 } 2594 2595 /** 2596 * Returns the earliest file system time that could be recorded for an event occurring at this 2597 * instant. This truncates fractional seconds because most host file systems do not use precise 2598 * timestamps for file metadata. 2599 * 2600 * It also pads the result by 200 milliseconds because the host and file system may use different 2601 * clocks, allowing the time on the CPU to drift ahead of the time on the file system. 2602 */ 2603 private fun Instant.minFileSystemTime(): Instant { 2604 val paddedInstant = minus(200.milliseconds) 2605 return fromEpochSeconds(paddedInstant.epochSeconds) 2606 } 2607 2608 /** 2609 * Returns the latest file system time that could be recorded for an event occurring at this 2610 * instant. This adds 2 seconds and truncates fractional seconds because file systems may defer 2611 * assigning the timestamp. 2612 * 2613 * It also pads the result by 200 milliseconds because the host and file system may use different 2614 * clocks, allowing the time on the CPU to drift behind the time on the file system. 2615 * 2616 * https://docs.microsoft.com/en-us/windows/win32/sysinfo/file-times 2617 */ 2618 private fun Instant.maxFileSystemTime(): Instant { 2619 val paddedInstant = plus(200.milliseconds) 2620 return fromEpochSeconds(paddedInstant.plus(2.seconds).epochSeconds) 2621 } 2622 2623 /** 2624 * Attempt to advance the clock so that any already-issued timestamps will not collide with 2625 * timestamps yet to be issued. 2626 */ 2627 private fun tryAdvanceTime() { 2628 if (clock is FakeClock) clock.sleep(1.minutes) 2629 } 2630 2631 private fun assertInRange(sampled: Instant?, minTime: Instant, maxTime: Instant) { 2632 if (sampled == null) return 2633 val minFsTime = minTime.minFileSystemTime() 2634 val maxFsTime = maxTime.maxFileSystemTime() 2635 assertTrue("expected $sampled in $minFsTime..$maxFsTime (relaxed from $minTime..$maxTime)") { 2636 sampled in minFsTime..maxFsTime 2637 } 2638 } 2639 2640 private fun isJvmFileSystemOnWindows(): Boolean { 2641 return windowsLimitations && fileSystem::class.simpleName == "JvmSystemFileSystem" 2642 } 2643 2644 private fun isJimFileSystem(): Boolean { 2645 return "JimfsFileSystem" in fileSystem.toString() 2646 } 2647 2648 private fun isNodeJsFileSystemOnWindows(): Boolean { 2649 return windowsLimitations && fileSystem::class.simpleName == "NodeJsFileSystem" 2650 } 2651 } 2652