Skip to content

Commit

Permalink
Merge pull request square#858 from square/jwilson.1228.isRoot
Browse files Browse the repository at this point in the history
Path.isRoot returns true for / and C:\
  • Loading branch information
swankjesse authored Dec 28, 2020
2 parents 2d67d0e + 5b6c70c commit 4715ea0
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 9 deletions.
19 changes: 16 additions & 3 deletions okio-testing/src/commonMain/kotlin/okio/FakeFilesystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ class FakeFilesystem(
private val workingDirectory: Path = (if (windowsLimitations) "F:\\".toPath() else "/".toPath())
) : Filesystem() {

init {
require(workingDirectory.isAbsolute) {
"expected an absolute path but was $workingDirectory"
}
}

/** Keys are canonical paths. Each value is either a [Directory] or a [ByteString]. */
private val elements = mutableMapOf<Path, Element>()

Expand All @@ -53,7 +59,7 @@ class FakeFilesystem(
get() {
val result = mutableSetOf<Path>()
for (path in elements.keys) {
if (path.parent == null) continue
if (path.isRoot) continue
result += path
}
return result
Expand Down Expand Up @@ -81,7 +87,14 @@ class FakeFilesystem(

override fun metadataOrNull(path: Path): FileMetadata? {
val canonicalPath = workingDirectory / path
val element = elements[canonicalPath]
var element = elements[canonicalPath]

// If the path is a root, create it on demand.
if (element == null && path.isRoot) {
element = Directory(createdAt = clock.now())
elements[path] = element
}

return element?.metadata
}

Expand Down Expand Up @@ -210,7 +223,7 @@ class FakeFilesystem(
if (element is Directory) return element

// If the path is a root, create it on demand.
if (element == null && path.parent == null && path.isAbsolute) {
if (element == null && path.isRoot) {
val root = Directory(createdAt = clock.now())
elements[path] = root
return root
Expand Down
2 changes: 1 addition & 1 deletion okio/src/commonMain/kotlin/okio/Filesystem.kt
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ abstract class Filesystem {
// Compute the sequence of directories to create.
val directories = ArrayDeque<Path>()
var path: Path? = dir
while (path != null && !isDirectory(path)) {
while (path != null && !exists(path)) {
directories.addFirst(path)
path = path.parent
}
Expand Down
17 changes: 12 additions & 5 deletions okio/src/commonMain/kotlin/okio/Path.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ import okio.Path.Companion.toPath
* There are some special cases when working with relative paths.
*
* On Windows, each volume (like `A:\` and `C:\`) has its own current working directory. A path
* prefixed with a volume letter and colon but no slash (like `A:essay.doc`) is relative to the
* prefixed with a volume letter and colon but no slash (like `A:letter.doc`) is relative to the
* working directory on the named volume. For example, if the working directory on `A:\` is
* `A:\jessewilson`, then the path `A:essay.doc` resolves to `A:\jessewilson\essay.doc`.
* `A:\jesse`, then the path `A:letter.doc` resolves to `A:\jesse\letter.doc`.
*
* The path string `C:\Windows` is an absolute path when following Windows rules and a relative
* path when following UNIX rules. For example, if the current working directory is
* `/Users/jwilson`, then `C:\Windows` resolves to `/Users/jwilson/C:/Windows`.
* `/Users/jesse`, then `C:\Windows` resolves to `/Users/jesse/C:/Windows`.
*
* This class decides which rules to follow by inspecting the first slash character in the path
* string. If the path contains no slash characters, it uses the host platform's rules. Or you may
Expand Down Expand Up @@ -176,7 +176,7 @@ class Path private constructor(
if (bytes.size == 3) return null // "C:\" has no parent.
return Path(slash, bytes.substring(endIndex = 3)) // Keep the trailing '\' in C:\.
}
lastSlash == 1 && bytes.startsWith(BACKSLASH_BACKSLASH) -> {
lastSlash == 1 && bytes.startsWith(BACKSLASH) -> {
return null // "\\server" is a UNC path with no parent.
}
lastSlash == -1 && volumeLetter != null -> {
Expand All @@ -195,6 +195,14 @@ class Path private constructor(
}
}

/**
* Returns true if this is an absolute path with no parent. UNIX paths have a single root, `/`.
* Each volume on Windows is its own root, like `C:\` and `D:\`. Windows UNC paths like `\\server`
* are also roots.
*/
val isRoot: Boolean
get() = parent == null && isAbsolute

private fun lastSegmentIsDotDot(): Boolean {
if (bytes.endsWith(DOT_DOT)) {
if (bytes.size == 2) return true // ".." is the whole string.
Expand Down Expand Up @@ -242,7 +250,6 @@ class Path private constructor(
companion object {
private val SLASH = "/".encodeUtf8()
private val BACKSLASH = "\\".encodeUtf8()
private val BACKSLASH_BACKSLASH = "\\".encodeUtf8()
private val ANY_SLASH = "/\\".encodeUtf8()
private val DOT = ".".encodeUtf8()
private val DOT_DOT = "..".encodeUtf8()
Expand Down
58 changes: 58 additions & 0 deletions okio/src/commonTest/kotlin/okio/FakeFilesystemTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package okio
import okio.Path.Companion.toPath
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
import kotlin.time.ExperimentalTime
import kotlin.time.minutes
Expand Down Expand Up @@ -150,4 +151,61 @@ abstract class FakeFilesystemTest internal constructor(
assertEquals(modifiedAt, metadata.lastModifiedAt)
assertEquals(accessedAt, metadata.lastAccessedAt)
}

@Test
fun createDirectoriesForVolumeLetterRoot() {
val path = "X:\\".toPath()
filesystem.createDirectories(path)
assertTrue(filesystem.metadata(path).isDirectory)
}

@Test
fun createDirectoriesForChildOfVolumeLetterRoot() {
val path = "X:\\path".toPath()
filesystem.createDirectories(path)
assertTrue(filesystem.metadata(path).isDirectory)
}

@Test
fun createDirectoriesForUnixRoot() {
val path = "/".toPath()
filesystem.createDirectories(path)
assertTrue(filesystem.metadata(path).isDirectory)
}

@Test
fun createDirectoriesForChildOfUnixRoot() {
val path = "/path".toPath()
filesystem.createDirectories(path)
assertTrue(filesystem.metadata(path).isDirectory)
}

@Test
fun createDirectoriesForUncRoot() {
val path = "\\\\server".toPath()
filesystem.createDirectories(path)
assertTrue(filesystem.metadata(path).isDirectory)
}

@Test
fun createDirectoriesForChildOfUncRoot() {
val path = "\\\\server\\project".toPath()
filesystem.createDirectories(path)
assertTrue(filesystem.metadata(path).isDirectory)
}

@Test
fun workingDirectoryMustBeAbsolute() {
val exception = assertFailsWith<IllegalArgumentException> {
FakeFilesystem(workingDirectory = "some/relative/path".toPath())
}
assertEquals("expected an absolute path but was some/relative/path", exception.message)
}

@Test
fun metadataForRootsGeneratedOnDemand() {
assertTrue(filesystem.metadata("X:\\".toPath()).isDirectory)
assertTrue(filesystem.metadata("/".toPath()).isDirectory)
assertTrue(filesystem.metadata("\\\\server".toPath()).isDirectory)
}
}
18 changes: 18 additions & 0 deletions okio/src/commonTest/kotlin/okio/PathTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("", path.name)
assertTrue(path.isAbsolute)
assertTrue(path.isRoot)
}

@Test
Expand All @@ -42,6 +43,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("todo.txt", path.name)
assertTrue(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -52,6 +54,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("todo.txt", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -62,6 +65,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("todo.txt", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -72,6 +76,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("..", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -82,6 +87,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("", path.name)
assertTrue(path.isAbsolute)
assertTrue(path.isRoot)
}

@Test
Expand All @@ -92,6 +98,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("", path.name)
assertTrue(path.isAbsolute)
assertTrue(path.isRoot)
}

@Test
Expand All @@ -102,6 +109,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("..", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -112,6 +120,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals(".", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -122,6 +131,7 @@ class PathTest {
assertEquals('C', path.volumeLetter)
assertEquals("", path.name)
assertTrue(path.isAbsolute)
assertTrue(path.isRoot)
}

@Test
Expand All @@ -132,6 +142,7 @@ class PathTest {
assertEquals('C', path.volumeLetter)
assertEquals("notepad.exe", path.name)
assertTrue(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -142,6 +153,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("", path.name)
assertTrue(path.isAbsolute)
assertTrue(path.isRoot)
}

@Test
Expand All @@ -152,6 +164,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("notepad.exe", path.name)
assertTrue(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -162,6 +175,7 @@ class PathTest {
assertEquals('C', path.volumeLetter)
assertEquals("notepad.exe", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -172,6 +186,7 @@ class PathTest {
assertEquals('C', path.volumeLetter)
assertEquals("", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -182,6 +197,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("notepad.exe", path.name)
assertFalse(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand All @@ -192,6 +208,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("server", path.name)
assertTrue(path.isAbsolute)
assertTrue(path.isRoot)
}

@Test
Expand All @@ -202,6 +219,7 @@ class PathTest {
assertNull(path.volumeLetter)
assertEquals("notes.txt", path.name)
assertTrue(path.isAbsolute)
assertFalse(path.isRoot)
}

@Test
Expand Down

0 comments on commit 4715ea0

Please sign in to comment.