Skip to content

Commit 5f76bba

Browse files
authored
test: initial wintertc common minimum api tests (#1263)
test: add initial wintertc tests test: add js test for common minimum interfaces test: add js test for common minimum globals Relates-to: #1259 Relates-to: #1260 Signed-off-by: Sam Gammon <[email protected]>
1 parent 4e064b3 commit 5f76bba

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed

packages/graalvm/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,9 @@ dependencies {
619619
testImplementation(libs.junit.jupiter.api)
620620
testImplementation(libs.junit.jupiter.params)
621621
testImplementation(mn.micronaut.test.junit5)
622+
testImplementation(projects.packages.graalvmJs)
623+
testImplementation(projects.packages.graalvmTs)
624+
testImplementation(projects.packages.graalvmWasm)
622625
testImplementation(projects.packages.graalvmPy)
623626
testRuntimeOnly(libs.junit.jupiter.engine)
624627
testImplementation(libs.jna.jpms)
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
* Copyright (c) 2024-2025 Elide Technologies, Inc.
3+
*
4+
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
*
7+
* https://opensource.org/license/mit/
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
* License for the specific language governing permissions and limitations under the License.
12+
*/
13+
package elide.runtime.winter
14+
15+
import org.graalvm.polyglot.Context
16+
import org.graalvm.polyglot.Source
17+
import org.junit.jupiter.api.Assumptions
18+
import org.junit.jupiter.api.DynamicTest
19+
import org.junit.jupiter.api.TestFactory
20+
import jakarta.inject.Inject
21+
import kotlinx.coroutines.test.runTest
22+
import kotlin.test.assertFalse
23+
import kotlin.test.assertNotNull
24+
import kotlin.use
25+
import elide.runtime.core.DelicateElideApi
26+
import elide.runtime.core.PolyglotEngine
27+
import elide.runtime.core.PolyglotEngineConfiguration.HostAccess.ALLOW_ALL
28+
import elide.runtime.gvm.GraalVMGuest
29+
import elide.runtime.gvm.internals.IntrinsicsManager
30+
import elide.runtime.gvm.internals.intrinsics.js.AbstractJsIntrinsic
31+
import elide.runtime.gvm.js.AbstractJsTest
32+
import elide.runtime.intrinsics.GuestIntrinsic.MutableIntrinsicBindings
33+
import elide.runtime.intrinsics.Symbol
34+
import elide.runtime.plugins.js.JavaScript
35+
import elide.runtime.plugins.vfs.vfs
36+
import elide.runtime.plugins.wasm.Wasm
37+
import elide.testing.annotations.TestCase
38+
39+
private const val ENABLE_POLYFILLS = true
40+
private const val ENABLE_SUPPRESSIONS = true
41+
42+
@TestCase internal class CommonMinimumTest : AbstractJsTest() {
43+
private val polyfillsContent = requireNotNull(javaClass.getResource(
44+
"/META-INF/elide/embedded/runtime/js/polyfills.js"
45+
)) {
46+
"Failed to locate JS polyfills"
47+
}.readText()
48+
49+
private val polyfillsSrc = Source.create("js", polyfillsContent)
50+
51+
@Inject private lateinit var intrinsics: IntrinsicsManager
52+
53+
// Symbols which are expected to be missing.
54+
private val expectMissingGlobals = sortedSetOf<String>()
55+
56+
// Symbols which can be missing.
57+
private val allowMissingGlobals = sortedSetOf<String>(
58+
"CompressionStream",
59+
"Crypto",
60+
"CryptoKey",
61+
"DOMException",
62+
"DecompressionStream",
63+
"Event",
64+
"FormData",
65+
"SubtleCrypto",
66+
"TextDecoderStream",
67+
"TextEncoderStream",
68+
"URLPattern",
69+
"WebAssembly.Global",
70+
"WebAssembly.Instance",
71+
"WebAssembly.Memory",
72+
"WebAssembly.Module",
73+
"WebAssembly.Table",
74+
"WebAssembly.compile",
75+
"WebAssembly.compileStreaming",
76+
"WebAssembly.instantiate",
77+
"WebAssembly.instantiateStreaming",
78+
"WebAssembly.validate",
79+
"navigator.userAgent",
80+
"structuredClone",
81+
)
82+
83+
// Minimum Common API §3.1: Interfaces.
84+
// See: https://min-common-api.proposal.wintertc.org/#api-index
85+
private val minimumCommonInterfaces = sortedSetOf(
86+
"AbortController",
87+
"AbortSignal",
88+
"Blob",
89+
"ByteLengthQueuingStrategy",
90+
"CompressionStream",
91+
"CountQueuingStrategy",
92+
"Crypto",
93+
"CryptoKey",
94+
"DecompressionStream",
95+
"DOMException",
96+
"Event",
97+
"EventTarget",
98+
"File",
99+
"FormData",
100+
"Headers",
101+
"ReadableByteStreamController",
102+
"ReadableStream",
103+
"ReadableStreamBYOBReader",
104+
"ReadableStreamBYOBRequest",
105+
"ReadableStreamDefaultController",
106+
"ReadableStreamDefaultReader",
107+
"Request",
108+
"Response",
109+
"SubtleCrypto",
110+
"TextDecoder",
111+
"TextDecoderStream",
112+
"TextEncoder",
113+
"TextEncoderStream",
114+
"TransformStream",
115+
"TransformStreamDefaultController",
116+
"URL",
117+
"URLPattern",
118+
"URLSearchParams",
119+
"WebAssembly.Global",
120+
"WebAssembly.Instance",
121+
"WebAssembly.Memory",
122+
"WebAssembly.Module",
123+
"WebAssembly.Table",
124+
"WritableStream",
125+
"WritableStreamDefaultController",
126+
"WritableStreamDefaultWriter"
127+
)
128+
129+
// Minimum Common API §3.2: Global methods / properties.
130+
// See: https://min-common-api.proposal.wintertc.org/#api-index
131+
private val commonMinimumMethodsAndProperties = sortedSetOf(
132+
"globalThis",
133+
"atob",
134+
"btoa",
135+
"clearInterval",
136+
"clearTimeout",
137+
"console",
138+
"crypto",
139+
"fetch",
140+
"navigator.userAgent",
141+
"performance.now",
142+
"performance.timeOrigin",
143+
"queueMicrotask",
144+
"setInterval",
145+
"setTimeout",
146+
"structuredClone",
147+
"WebAssembly.compile",
148+
"WebAssembly.compileStreaming",
149+
"WebAssembly.instantiate",
150+
"WebAssembly.instantiateStreaming",
151+
"WebAssembly.validate",
152+
)
153+
154+
@DelicateElideApi
155+
private fun withFreshContext(block: suspend Context.() -> Unit): Unit = PolyglotEngine {
156+
hostAccess = ALLOW_ALL
157+
158+
vfs {
159+
useHost = true
160+
}
161+
162+
install(Wasm)
163+
install(JavaScript) {
164+
npm {
165+
enabled = true
166+
modulesPath = System.getenv("PWD")
167+
wasm = true
168+
}
169+
}
170+
}.let { engine ->
171+
engine.acquire {
172+
build().use {
173+
runTest {
174+
block.invoke(it)
175+
}
176+
}
177+
}
178+
Unit
179+
}
180+
181+
@DelicateElideApi
182+
private fun executeGuest(
183+
stdEngine: Boolean = true,
184+
block: () -> String,
185+
) {
186+
if (stdEngine) this.executeGuest(bind = true) {
187+
StringBuilder().apply {
188+
if (ENABLE_POLYFILLS) appendLine(polyfillsContent)
189+
append(block())
190+
}.toString()
191+
}.let {
192+
it.doesNotFail()
193+
it.returnValue().let { symbolValue ->
194+
assertNotNull(symbolValue, "should get value from guest execution")
195+
assertFalse(symbolValue.isNull, "guest value should not be `null`")
196+
assertFalse(symbolValue.isBoolean && !symbolValue.asBoolean(), "value should not be `false`")
197+
}
198+
} else withFreshContext {
199+
if (ENABLE_POLYFILLS) eval(polyfillsSrc)
200+
eval(Source.create("js", block.invoke())).let { value ->
201+
assertNotNull(value, "should get value from guest execution")
202+
assertFalse(value.isNull, "value should not be `null`")
203+
assertFalse(value.isBoolean && !value.asBoolean(), "value should not be `false`")
204+
}
205+
}
206+
}
207+
208+
@OptIn(DelicateElideApi::class)
209+
suspend fun SequenceScope<DynamicTest>.testFactory(globalName: String) {
210+
if (globalName !in expectMissingGlobals) yield(
211+
DynamicTest.dynamicTest(globalName) {
212+
if (ENABLE_SUPPRESSIONS && globalName in allowMissingGlobals) {
213+
Assumptions.abort<Unit>("Common Minimum API '$globalName' is known-missing")
214+
}
215+
executeGuest {
216+
// language=JavaScript
217+
"""
218+
function pluck() {
219+
return $globalName;
220+
}
221+
pluck();
222+
"""
223+
}
224+
}
225+
)
226+
}
227+
228+
@DelicateElideApi
229+
@TestFactory fun `minimum common api - interfaces`(): List<DynamicTest> = sequence<DynamicTest> {
230+
val symbols = intrinsics.resolver().resolve(GraalVMGuest.JAVASCRIPT, internals = true).toList()
231+
val mapped = mutableMapOf<Symbol, Any>()
232+
val bindings = MutableIntrinsicBindings.Factory.wrap(mapped)
233+
symbols.map { it as AbstractJsIntrinsic }.map {
234+
it.install(bindings)
235+
}
236+
minimumCommonInterfaces.forEach { globalName ->
237+
testFactory(globalName)
238+
}
239+
}.toList()
240+
241+
@DelicateElideApi
242+
@TestFactory fun `minimum common api - globals`(): List<DynamicTest> = sequence<DynamicTest> {
243+
val symbols = intrinsics.resolver().resolve(GraalVMGuest.JAVASCRIPT, internals = true).toList()
244+
val mapped = mutableMapOf<Symbol, Any>()
245+
val bindings = MutableIntrinsicBindings.Factory.wrap(mapped)
246+
symbols.map { it as AbstractJsIntrinsic }.map {
247+
it.install(bindings)
248+
}
249+
commonMinimumMethodsAndProperties.forEach { globalName ->
250+
testFactory(globalName)
251+
}
252+
}.toList()
253+
}

0 commit comments

Comments
 (0)