Skip to content

Commit 9fbab0f

Browse files
authored
Decode env variables in WASI tests (#1773)
* Decode env variables in WASI tests Also delete the obsolete Instant and Clock shims necessary because Wasi didn't have these types previously. * Fix some build bugs
1 parent 50abe89 commit 9fbab0f

14 files changed

Lines changed: 167 additions & 81 deletions

File tree

okio-testing-support/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ kotlin {
6464
.also { wasmMain ->
6565
wasmMain.dependsOn(commonMain)
6666
}
67+
val wasmWasiMain by getting {
68+
dependencies {
69+
implementation(project(":okio-wasifilesystem"))
70+
}
71+
}
6772
}
6873
}
6974
}

okio-testing-support/src/commonMain/kotlin/okio/AbstractFileSystemTest.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ import kotlin.test.assertFalse
2525
import kotlin.test.assertNull
2626
import kotlin.test.assertTrue
2727
import kotlin.test.fail
28+
import kotlin.time.Clock
2829
import kotlin.time.Duration.Companion.milliseconds
2930
import kotlin.time.Duration.Companion.minutes
3031
import kotlin.time.Duration.Companion.seconds
32+
import kotlin.time.Instant
3133
import okio.ByteString.Companion.encodeUtf8
3234
import okio.ByteString.Companion.toByteString
3335
import okio.Path.Companion.toPath
@@ -2705,7 +2707,7 @@ abstract class AbstractFileSystemTest(
27052707
*/
27062708
private fun Instant.minFileSystemTime(): Instant {
27072709
val paddedInstant = minus(200.milliseconds)
2708-
return fromEpochSeconds(paddedInstant.epochSeconds)
2710+
return Instant.fromEpochSeconds(paddedInstant.epochSeconds)
27092711
}
27102712

27112713
/**
@@ -2720,7 +2722,7 @@ abstract class AbstractFileSystemTest(
27202722
*/
27212723
private fun Instant.maxFileSystemTime(): Instant {
27222724
val paddedInstant = plus(200.milliseconds)
2723-
return fromEpochSeconds(paddedInstant.plus(2.seconds).epochSeconds)
2725+
return Instant.fromEpochSeconds(paddedInstant.plus(2.seconds).epochSeconds)
27242726
}
27252727

27262728
/**

okio-testing-support/src/commonMain/kotlin/okio/FakeClock.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
*/
1616
package okio
1717

18+
import kotlin.time.Clock
1819
import kotlin.time.Duration
20+
import kotlin.time.Instant
1921

2022
class FakeClock : Clock {
21-
var time = fromEpochSeconds(1609459200L) // 2021-01-01T00:00:00Z
23+
var time = Instant.fromEpochSeconds(1609459200L) // 2021-01-01T00:00:00Z
2224

2325
override fun now() = time
2426

okio-testing-support/src/commonMain/kotlin/okio/TestingCommon.kt

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ package okio
1717

1818
import kotlin.random.Random
1919
import kotlin.test.assertEquals
20-
import kotlin.time.Duration
20+
import kotlin.time.Instant
2121
import okio.ByteString.Companion.toByteString
2222
import okio.Path.Companion.toPath
2323

@@ -45,47 +45,21 @@ expect fun isWasm(): Boolean
4545
val FileMetadata.createdAt: Instant?
4646
get() {
4747
val createdAt = createdAtMillis ?: return null
48-
return fromEpochMilliseconds(createdAt)
48+
return Instant.fromEpochMilliseconds(createdAt)
4949
}
5050

5151
val FileMetadata.lastModifiedAt: Instant?
5252
get() {
5353
val lastModifiedAt = lastModifiedAtMillis ?: return null
54-
return fromEpochMilliseconds(lastModifiedAt)
54+
return Instant.fromEpochMilliseconds(lastModifiedAt)
5555
}
5656

5757
val FileMetadata.lastAccessedAt: Instant?
5858
get() {
5959
val lastAccessedAt = lastAccessedAtMillis ?: return null
60-
return fromEpochMilliseconds(lastAccessedAt)
60+
return Instant.fromEpochMilliseconds(lastAccessedAt)
6161
}
6262

63-
/*
64-
* This file contains some declarations from kotlinx.datetime used by [AbstractFileSystemTest], but
65-
* that we can't use because that library isn't yet available for WASM. We should delete these when
66-
* WASM is supported in kotlinx.datetime.
67-
*/
68-
69-
expect interface Clock {
70-
fun now(): Instant
71-
}
72-
73-
expect class Instant : Comparable<Instant> {
74-
val epochSeconds: Long
75-
76-
operator fun plus(duration: Duration): Instant
77-
78-
operator fun minus(duration: Duration): Instant
79-
80-
override operator fun compareTo(other: Instant): Int
81-
}
82-
83-
expect fun fromEpochSeconds(
84-
epochSeconds: Long,
85-
): Instant
86-
87-
expect fun fromEpochMilliseconds(epochMilliseconds: Long): Instant
88-
8963
expect val FileSystem.isFakeFileSystem: Boolean
9064

9165
expect val FileSystem.allowSymlinks: Boolean

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,6 @@ package okio
1717

1818
import okio.fakefilesystem.FakeFileSystem
1919

20-
actual typealias Clock = kotlin.time.Clock
21-
22-
actual typealias Instant = kotlin.time.Instant
23-
24-
actual fun fromEpochSeconds(
25-
epochSeconds: Long,
26-
) = Instant.fromEpochSeconds(epochSeconds)
27-
28-
actual fun fromEpochMilliseconds(
29-
epochMilliseconds: Long,
30-
) = Instant.fromEpochMilliseconds(epochMilliseconds)
31-
3220
actual val FileSystem.isFakeFileSystem: Boolean
3321
get() = this is FakeFileSystem
3422

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (C) 2026 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+
actual fun getEnv(name: String): String? = error("unexpected call")

okio-testing-support/src/wasmMain/kotlin/okio/TestingWasm.kt

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,16 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
@file:OptIn(ExperimentalTime::class)
17+
1618
package okio
1719

18-
import kotlin.time.Duration
20+
import kotlin.time.ExperimentalTime
1921

2022
actual fun isBrowser() = false
2123

2224
actual fun isWasm() = true
2325

24-
actual interface Clock {
25-
actual fun now(): Instant
26-
}
27-
28-
actual class Instant(
29-
private val epochMilliseconds: Long,
30-
) : Comparable<Instant> {
31-
actual val epochSeconds: Long
32-
get() = epochMilliseconds / 1_000L
33-
34-
actual operator fun plus(duration: Duration) =
35-
Instant(epochMilliseconds + duration.inWholeMilliseconds)
36-
37-
actual operator fun minus(duration: Duration) =
38-
Instant(epochMilliseconds - duration.inWholeMilliseconds)
39-
40-
actual override fun compareTo(other: Instant) =
41-
epochMilliseconds.compareTo(other.epochMilliseconds)
42-
}
43-
44-
actual fun fromEpochSeconds(epochSeconds: Long) =
45-
Instant(epochSeconds * 1_000L)
46-
47-
actual fun fromEpochMilliseconds(epochMilliseconds: Long) =
48-
Instant(epochMilliseconds)
49-
5026
actual val FileSystem.isFakeFileSystem: Boolean
5127
get() = false
5228

@@ -59,5 +35,3 @@ actual val FileSystem.allowReadsWhileWriting: Boolean
5935
actual var FileSystem.workingDirectory: Path
6036
get() = error("unexpected call")
6137
set(_) = error("unexpected call")
62-
63-
actual fun getEnv(name: String): String? = error("unexpected call")

okio-testing-support/src/wasmWasiMain/kotlin/okio/WasiClock.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package okio
1717

18+
import kotlin.time.Clock
19+
import kotlin.time.Instant
1820
import kotlin.wasm.unsafe.UnsafeWasmMemoryApi
1921
import kotlin.wasm.unsafe.withScopedMemoryAllocator
2022
import okio.internal.preview1.clock_time_get
@@ -33,7 +35,11 @@ object WasiClock : Clock {
3335
if (errno != 0) throw IllegalStateException("failed to get now: $errno")
3436

3537
val nanos = returnPointer.loadLong()
36-
return Instant(epochMilliseconds = nanos / 1_000_000L)
38+
val seconds = nanos / 1_000_000_000L
39+
return Instant.fromEpochSeconds(
40+
epochSeconds = seconds,
41+
nanosecondAdjustment = (nanos - seconds * 1_000_000_000L).toInt(),
42+
)
3743
}
3844
}
3945
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (C) 2026 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+
@file:Suppress(
17+
"INVISIBLE_REFERENCE",
18+
)
19+
20+
package okio
21+
22+
import kotlin.wasm.unsafe.Pointer
23+
import kotlin.wasm.unsafe.UnsafeWasmMemoryApi
24+
import kotlin.wasm.unsafe.withScopedMemoryAllocator
25+
import okio.internal.ErrnoException
26+
import okio.internal.preview1.environ_get
27+
import okio.internal.preview1.environ_sizes_get
28+
import okio.internal.readString
29+
30+
@OptIn(UnsafeWasmMemoryApi::class)
31+
val env: Map<String, String> by lazy {
32+
withScopedMemoryAllocator { allocator ->
33+
val entryCountPointer = allocator.allocate(4)
34+
val byteCountPointer = allocator.allocate(4)
35+
val sizesGetErrno = environ_sizes_get(
36+
returnEntryCountPointer = entryCountPointer.address.toInt(),
37+
returnByteCountPointer = byteCountPointer.address.toInt(),
38+
)
39+
if (sizesGetErrno != 0) throw ErrnoException(sizesGetErrno.toShort())
40+
41+
val entryCount = entryCountPointer.loadInt()
42+
val byteCount = byteCountPointer.loadInt()
43+
44+
val entryPointersPointer = allocator.allocate(entryCount * 4)
45+
val entryBytesPointer = allocator.allocate(byteCount)
46+
val getErrno = environ_get(
47+
entryPointersPointer.address.toInt(),
48+
entryBytesPointer.address.toInt(),
49+
)
50+
if (getErrno != 0) throw ErrnoException(getErrno.toShort())
51+
52+
buildMap {
53+
for (i in 0 until entryCount) {
54+
val entryPointer = Pointer((entryPointersPointer + i * 4).loadInt().toUInt())
55+
val entryString = entryPointer.readNullTerminated()
56+
val eq = entryString.indexOf('=')
57+
if (eq != -1) {
58+
put(entryString.take(eq), entryString.substring(eq + 1))
59+
}
60+
}
61+
}
62+
}
63+
}
64+
65+
@OptIn(UnsafeWasmMemoryApi::class)
66+
internal fun Pointer.readNullTerminated(): String {
67+
var byteCount = 0
68+
while ((this + byteCount).loadByte().toInt() != 0) {
69+
byteCount++
70+
}
71+
return readString(byteCount)
72+
}
73+
74+
actual fun getEnv(name: String) = env[name]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2019-2023 the Contributors to the WASI Specification
2+
// This file is adapted from the WASI preview1 spec here:
3+
// https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md
4+
5+
package okio.internal.preview1
6+
7+
/**
8+
* environ_sizes_get() -> Result<(size, size), errno>
9+
*
10+
* Return environment variable data sizes.
11+
*/
12+
@WasmImport("wasi_snapshot_preview1", "environ_sizes_get")
13+
internal external fun environ_sizes_get(
14+
returnEntryCountPointer: Int,
15+
returnByteCountPointer: Int,
16+
): Int // should be Short??
17+
18+
/**
19+
* environ_get(environ: Pointer<Pointer<u8>>, environ_buf: Pointer<u8>) -> Result<(), errno>
20+
*
21+
* Read environment variable data.
22+
* The sizes of the buffers should match that returned by [`environ_sizes_get`](#environ_sizes_get).
23+
* Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s.
24+
*/
25+
@WasmImport("wasi_snapshot_preview1", "environ_get")
26+
internal external fun environ_get(
27+
environ: Int,
28+
environ_buf: Int,
29+
): Int // should be Short??

0 commit comments

Comments
 (0)