Skip to content

Commit

Permalink
Get AbstractFileSystemTest running on WASM (#1316)
Browse files Browse the repository at this point in the history
* Get AbstractFileSystemTest running on WASM

* Get many tests from AbstractFileSystemTest passing

Still some follow-ups to do.

* Fixup workingDirectory delegation

* Update okio-testing-support/src/nonWasmMain/kotlin/okio/TestingNonWasm.kt

Co-authored-by: Benoît Quenaudon <bquenaudon@squareup.com>

---------

Co-authored-by: Benoît Quenaudon <bquenaudon@squareup.com>
  • Loading branch information
squarejesse and oldergod authored Jul 29, 2023
1 parent 0709c1f commit 0aac566
Show file tree
Hide file tree
Showing 14 changed files with 330 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,9 @@ import kotlin.test.fail
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import okio.ByteString.Companion.encodeUtf8
import okio.ByteString.Companion.toByteString
import okio.Path.Companion.toPath
import okio.fakefilesystem.FakeFileSystem

/** This test assumes that okio-files/ is the current working directory when executed. */
abstract class AbstractFileSystemTest(
Expand All @@ -47,6 +44,7 @@ abstract class AbstractFileSystemTest(
) {
val base: Path = temporaryDirectory / "${this::class.simpleName}-${randomToken(16)}"
private val isNodeJsFileSystem = fileSystem::class.simpleName?.startsWith("NodeJs") ?: false
private val isWasiFileSystem = fileSystem::class.simpleName?.startsWith("Wasi") ?: false
private val isWrappingJimFileSystem = this::class.simpleName?.contains("JimFileSystem") ?: false

@BeforeTest
Expand All @@ -68,7 +66,8 @@ abstract class AbstractFileSystemTest(

@Test
fun canonicalizeDotReturnsCurrentWorkingDirectory() {
if (fileSystem is FakeFileSystem || fileSystem is ForwardingFileSystem) return
if (fileSystem.isFakeFileSystem || fileSystem is ForwardingFileSystem) return
if (isWasiFileSystem) return // Canonicalize is limited on WASI.
val cwd = fileSystem.canonicalize(".".toPath())
val cwdString = cwd.toString()
val slash = Path.DIRECTORY_SEPARATOR
Expand Down Expand Up @@ -234,6 +233,7 @@ abstract class AbstractFileSystemTest(
@Test
fun canonicalizeReturnsShallowerPath() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Canonicalize is limited on WASI.
val base = fileSystem.canonicalize(base)

val expected = base / "a.txt"
Expand Down Expand Up @@ -262,7 +262,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listOnRelativePathReturnsRelativePaths() {
// Make sure there's always at least one file so our assertion is useful.
if (fileSystem is FakeFileSystem) {
if (fileSystem.isFakeFileSystem) {
val workingDirectory = "/directory".toPath()
fileSystem.createDirectory(workingDirectory)
fileSystem.workingDirectory = workingDirectory
Expand All @@ -273,6 +273,8 @@ abstract class AbstractFileSystemTest(
fileSystem.write("a.txt".toPath()) {
writeUtf8("hello, world!")
}
} else if (isWasiFileSystem) {
return // TODO: implement this behavior.
}

val entries = fileSystem.list(".".toPath())
Expand All @@ -282,18 +284,17 @@ abstract class AbstractFileSystemTest(
@Test
fun listOnRelativePathWhichIsNotDotReturnsRelativePaths() {
if (isNodeJsFileSystem) return
if (isWasiFileSystem) return // TODO: implement this behavior.

// Make sure there's always at least one file so our assertion is useful. We copy the first 2
// entries of the real working directory of the JVM to validate the results on all environment.
if (
fileSystem is FakeFileSystem ||
fileSystem is ForwardingFileSystem && fileSystem.delegate is FakeFileSystem
fileSystem.isFakeFileSystem ||
fileSystem is ForwardingFileSystem && fileSystem.delegate.isFakeFileSystem
) {
val workingDirectory = "/directory".toPath()
fileSystem.createDirectory(workingDirectory)
(fileSystem as? FakeFileSystem)?.workingDirectory = workingDirectory
((fileSystem as? ForwardingFileSystem)?.delegate as? FakeFileSystem)?.workingDirectory =
workingDirectory
fileSystem.workingDirectory = workingDirectory
val apiDir = "api".toPath()
fileSystem.createDirectory(apiDir)
fileSystem.write(apiDir / "okio.api".toPath()) {
Expand Down Expand Up @@ -335,18 +336,17 @@ abstract class AbstractFileSystemTest(
@Test
fun listOrNullOnRelativePathWhichIsNotDotReturnsRelativePaths() {
if (isNodeJsFileSystem) return
if (isWasiFileSystem) return // TODO: implement this behavior.

// Make sure there's always at least one file so our assertion is useful. We copy the first 2
// entries of the real working directory of the JVM to validate the results on all environment.
if (
fileSystem is FakeFileSystem ||
fileSystem is ForwardingFileSystem && fileSystem.delegate is FakeFileSystem
fileSystem.isFakeFileSystem ||
fileSystem is ForwardingFileSystem && fileSystem.delegate.isFakeFileSystem
) {
val workingDirectory = "/directory".toPath()
fileSystem.createDirectory(workingDirectory)
(fileSystem as? FakeFileSystem)?.workingDirectory = workingDirectory
((fileSystem as? ForwardingFileSystem)?.delegate as? FakeFileSystem)?.workingDirectory =
workingDirectory
fileSystem.workingDirectory = workingDirectory
val apiDir = "api".toPath()
fileSystem.createDirectory(apiDir)
fileSystem.write(apiDir / "okio.api".toPath()) {
Expand Down Expand Up @@ -431,7 +431,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listOrNullOnRelativePathReturnsRelativePaths() {
// Make sure there's always at least one file so our assertion is useful.
if (fileSystem is FakeFileSystem) {
if (fileSystem.isFakeFileSystem) {
val workingDirectory = "/directory".toPath()
fileSystem.createDirectory(workingDirectory)
fileSystem.workingDirectory = workingDirectory
Expand Down Expand Up @@ -607,6 +607,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listRecursivelyFollowsSymlinks() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val baseA = base / "a"
val baseAA = baseA / "a"
Expand Down Expand Up @@ -638,6 +639,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listRecursivelyOnSymlink() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val baseA = base / "a"
val baseAA = baseA / "a"
Expand Down Expand Up @@ -671,6 +673,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listRecursiveOnSymlinkWithSpecialCharacterNamedFiles() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val baseA = base / "ä"
val baseASuperSaiyan = baseA / "超サイヤ人"
Expand All @@ -688,6 +691,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listRecursivelyOnSymlinkCycleThrows() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val baseA = base / "a"
val baseAB = baseA / "b"
Expand Down Expand Up @@ -918,7 +922,7 @@ abstract class AbstractFileSystemTest(

@Test
fun appendingSinkDoesNotImpactExistingFile() {
if (fileSystem is FakeFileSystem && !fileSystem.allowReadsWhileWriting) return
if (fileSystem.isFakeFileSystem && !fileSystem.allowReadsWhileWriting) return

val path = base / "appending-sink-does-not-impact-existing-file"
path.writeUtf8("hello, world!\n")
Expand Down Expand Up @@ -962,7 +966,7 @@ abstract class AbstractFileSystemTest(

@Test
fun fileSinkFlush() {
if (fileSystem is FakeFileSystem && !fileSystem.allowReadsWhileWriting) return
if (fileSystem.isFakeFileSystem && !fileSystem.allowReadsWhileWriting) return

val path = base / "file-sink"
val sink = fileSystem.sink(path)
Expand Down Expand Up @@ -2355,6 +2359,7 @@ abstract class AbstractFileSystemTest(
@Test
fun openSymlinkSource() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val target = base / "symlink-target"
val source = base / "symlink-source"
Expand All @@ -2368,6 +2373,7 @@ abstract class AbstractFileSystemTest(
fun openSymlinkSink() {
if (!supportsSymlink()) return
if (isJimFileSystem()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val target = base / "symlink-target"
val source = base / "symlink-source"
Expand All @@ -2381,6 +2387,7 @@ abstract class AbstractFileSystemTest(
@Test
fun openFileWithDirectorySymlink() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val baseA = base / "a"
val baseAA = base / "a" / "a"
Expand All @@ -2396,6 +2403,7 @@ abstract class AbstractFileSystemTest(
@Test
fun openSymlinkFileHandle() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val target = base / "symlink-target"
val source = base / "symlink-source"
Expand All @@ -2410,6 +2418,7 @@ abstract class AbstractFileSystemTest(
@Test
fun listSymlinkDirectory() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val baseA = base / "a"
val baseAA = base / "a" / "a"
Expand All @@ -2427,6 +2436,7 @@ abstract class AbstractFileSystemTest(
@Test
fun symlinkFileLastAccessedAt() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val target = base / "symlink-target"
val source = base / "symlink-source"
Expand All @@ -2442,6 +2452,7 @@ abstract class AbstractFileSystemTest(
@Test
fun symlinkDirectoryLastAccessedAt() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val baseA = base / "a"
val baseAA = base / "a" / "a"
Expand Down Expand Up @@ -2472,6 +2483,7 @@ abstract class AbstractFileSystemTest(
@Test
fun moveSymlinkDoesntMoveTargetFile() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val target = base / "symlink-target"
val source1 = base / "symlink-source-1"
Expand Down Expand Up @@ -2513,6 +2525,7 @@ abstract class AbstractFileSystemTest(
@Test
fun followingRecursiveSymlinksIsOkay() {
if (!supportsSymlink()) return
if (isWasiFileSystem) return // Symlinks to absolute paths are broken on WASI.

val pathA = base / "symlink-a"
val pathB = base / "symlink-b"
Expand Down Expand Up @@ -2544,7 +2557,7 @@ abstract class AbstractFileSystemTest(
}

protected fun supportsSymlink(): Boolean {
if (fileSystem is FakeFileSystem) return fileSystem.allowSymlinks
if (fileSystem.isFakeFileSystem) return fileSystem.allowSymlinks
if (windowsLimitations) return false
return when (fileSystem::class.simpleName) {
"JvmSystemFileSystem",
Expand Down Expand Up @@ -2592,7 +2605,7 @@ abstract class AbstractFileSystemTest(
*/
private fun Instant.minFileSystemTime(): Instant {
val paddedInstant = minus(200.milliseconds)
return Instant.fromEpochSeconds(paddedInstant.epochSeconds)
return fromEpochSeconds(paddedInstant.epochSeconds)
}

/**
Expand All @@ -2607,7 +2620,7 @@ abstract class AbstractFileSystemTest(
*/
private fun Instant.maxFileSystemTime(): Instant {
val paddedInstant = plus(200.milliseconds)
return Instant.fromEpochSeconds(paddedInstant.plus(2.seconds).epochSeconds)
return fromEpochSeconds(paddedInstant.plus(2.seconds).epochSeconds)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@
package okio

import kotlin.time.Duration
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant

class FakeClock : Clock {
var time = Instant.parse("2021-01-01T00:00:00Z")
var time = fromEpochSeconds(1609459200L) // 2021-01-01T00:00:00Z

override fun now() = time

Expand Down
51 changes: 51 additions & 0 deletions okio-testing-support/src/commonMain/kotlin/okio/TestingCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package okio

import kotlin.random.Random
import kotlin.test.assertEquals
import kotlin.time.Duration
import okio.ByteString.Companion.toByteString

fun Char.repeat(count: Int): String {
Expand All @@ -39,3 +40,53 @@ fun randomToken(length: Int) = Random.nextBytes(length).toByteString(0, length).
expect fun isBrowser(): Boolean

expect fun isWasm(): Boolean

val FileMetadata.createdAt: Instant?
get() {
val createdAt = createdAtMillis ?: return null
return fromEpochMilliseconds(createdAt)
}

val FileMetadata.lastModifiedAt: Instant?
get() {
val lastModifiedAt = lastModifiedAtMillis ?: return null
return fromEpochMilliseconds(lastModifiedAt)
}

val FileMetadata.lastAccessedAt: Instant?
get() {
val lastAccessedAt = lastAccessedAtMillis ?: return null
return fromEpochMilliseconds(lastAccessedAt)
}

/*
* This file contains some declarations from kotlinx.datetime used by [AbstractFileSystemTest], but
* that we can't use because that library isn't yet available for WASM. We should delete these when
* WASM is supported in kotlinx.datetime.
*/

expect interface Clock {
fun now(): Instant
}

expect class Instant : Comparable<Instant> {
val epochSeconds: Long

operator fun plus(duration: Duration): Instant

operator fun minus(duration: Duration): Instant
}

expect fun fromEpochSeconds(
epochSeconds: Long,
): Instant

expect fun fromEpochMilliseconds(epochMilliseconds: Long): Instant

expect val FileSystem.isFakeFileSystem: Boolean

expect val FileSystem.allowSymlinks: Boolean

expect val FileSystem.allowReadsWhileWriting: Boolean

expect var FileSystem.workingDirectory: Path
55 changes: 55 additions & 0 deletions okio-testing-support/src/nonWasmMain/kotlin/okio/TestingNonWasm.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okio

import okio.fakefilesystem.FakeFileSystem

actual typealias Clock = kotlinx.datetime.Clock

actual typealias Instant = kotlinx.datetime.Instant

actual fun fromEpochSeconds(
epochSeconds: Long,
) = Instant.fromEpochSeconds(epochSeconds)

actual fun fromEpochMilliseconds(
epochMilliseconds: Long,
) = Instant.fromEpochMilliseconds(epochMilliseconds)

actual val FileSystem.isFakeFileSystem: Boolean
get() = this is FakeFileSystem

actual val FileSystem.allowSymlinks: Boolean
get() = (this as? FakeFileSystem)?.allowSymlinks == true

actual val FileSystem.allowReadsWhileWriting: Boolean
get() = (this as? FakeFileSystem)?.allowReadsWhileWriting == true

actual var FileSystem.workingDirectory: Path
get() {
return when (this) {
is FakeFileSystem -> workingDirectory
is ForwardingFileSystem -> delegate.workingDirectory
else -> error("cannot get working directory: $this")
}
}
set(value) {
when (this) {
is FakeFileSystem -> workingDirectory = value
is ForwardingFileSystem -> delegate.workingDirectory = value
else -> error("cannot set working directory: $this")
}
}
Loading

0 comments on commit 0aac566

Please sign in to comment.