Skip to content

Commit

Permalink
test: add initial wintertc tests
Browse files Browse the repository at this point in the history
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 <sam@elide.dev>
  • Loading branch information
sgammon committed Mar 8, 2025
1 parent ab3eccc commit f80dda9
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/graalvm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,9 @@ dependencies {
testImplementation(libs.junit.jupiter.api)
testImplementation(libs.junit.jupiter.params)
testImplementation(mn.micronaut.test.junit5)
testImplementation(projects.packages.graalvmJs)
testImplementation(projects.packages.graalvmTs)
testImplementation(projects.packages.graalvmWasm)
testImplementation(projects.packages.graalvmPy)
testRuntimeOnly(libs.junit.jupiter.engine)
testImplementation(libs.jna.jpms)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
*
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://opensource.org/license/mit/
*
* 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 elide.runtime.winter

import org.graalvm.polyglot.Context
import org.graalvm.polyglot.Source
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.TestFactory
import jakarta.inject.Inject
import kotlinx.coroutines.test.runTest
import kotlin.test.assertFalse
import kotlin.test.assertNotNull
import kotlin.use
import elide.runtime.core.DelicateElideApi
import elide.runtime.core.PolyglotEngine
import elide.runtime.core.PolyglotEngineConfiguration.HostAccess.ALLOW_ALL
import elide.runtime.gvm.GraalVMGuest
import elide.runtime.gvm.internals.IntrinsicsManager
import elide.runtime.gvm.internals.intrinsics.js.AbstractJsIntrinsic
import elide.runtime.gvm.js.AbstractJsTest
import elide.runtime.intrinsics.GuestIntrinsic.MutableIntrinsicBindings
import elide.runtime.intrinsics.Symbol
import elide.runtime.plugins.js.JavaScript
import elide.runtime.plugins.vfs.vfs
import elide.runtime.plugins.wasm.Wasm
import elide.testing.annotations.TestCase

private const val ENABLE_POLYFILLS = true
private const val ENABLE_SUPPRESSIONS = true

@TestCase internal class CommonMinimumTest : AbstractJsTest() {
private val polyfillsContent = requireNotNull(javaClass.getResource(
"/META-INF/elide/embedded/runtime/js/polyfills.js"
)) {
"Failed to locate JS polyfills"
}.readText()

private val polyfillsSrc = Source.create("js", polyfillsContent)

@Inject private lateinit var intrinsics: IntrinsicsManager

// Symbols which are expected to be missing.
private val expectMissingGlobals = sortedSetOf<String>()

// Symbols which can be missing.
private val allowMissingGlobals = sortedSetOf<String>(
"CompressionStream",
"Crypto",
"CryptoKey",
"DOMException",
"DecompressionStream",
"Event",
"FormData",
"SubtleCrypto",
"TextDecoderStream",
"TextEncoderStream",
"URLPattern",
"WebAssembly.Global",
"WebAssembly.Instance",
"WebAssembly.Memory",
"WebAssembly.Module",
"WebAssembly.Table",
"WebAssembly.compile",
"WebAssembly.compileStreaming",
"WebAssembly.instantiate",
"WebAssembly.instantiateStreaming",
"WebAssembly.validate",
"navigator.userAgent",
"queueMicrotask",
"structuredClone",
)

// Minimum Common API §3.1: Interfaces.
// See: https://min-common-api.proposal.wintertc.org/#api-index
private val minimumCommonInterfaces = sortedSetOf(
"AbortController",
"AbortSignal",
"Blob",
"ByteLengthQueuingStrategy",
"CompressionStream",
"CountQueuingStrategy",
"Crypto",
"CryptoKey",
"DecompressionStream",
"DOMException",
"Event",
"EventTarget",
"File",
"FormData",
"Headers",
"ReadableByteStreamController",
"ReadableStream",
"ReadableStreamBYOBReader",
"ReadableStreamBYOBRequest",
"ReadableStreamDefaultController",
"ReadableStreamDefaultReader",
"Request",
"Response",
"SubtleCrypto",
"TextDecoder",
"TextDecoderStream",
"TextEncoder",
"TextEncoderStream",
"TransformStream",
"TransformStreamDefaultController",
"URL",
"URLPattern",
"URLSearchParams",
"WebAssembly.Global",
"WebAssembly.Instance",
"WebAssembly.Memory",
"WebAssembly.Module",
"WebAssembly.Table",
"WritableStream",
"WritableStreamDefaultController",
"WritableStreamDefaultWriter"
)

// Minimum Common API §3.2: Global methods / properties.
// See: https://min-common-api.proposal.wintertc.org/#api-index
private val commonMinimumMethodsAndProperties = sortedSetOf(
"globalThis",
"atob",
"btoa",
"clearInterval",
"clearTimeout",
"console",
"crypto",
"fetch",
"navigator.userAgent",
"performance.now",
"performance.timeOrigin",
"queueMicrotask",
"setInterval",
"setTimeout",
"structuredClone",
"WebAssembly.compile",
"WebAssembly.compileStreaming",
"WebAssembly.instantiate",
"WebAssembly.instantiateStreaming",
"WebAssembly.validate",
)

@DelicateElideApi
private fun withFreshContext(block: suspend Context.() -> Unit): Unit = PolyglotEngine {
hostAccess = ALLOW_ALL

vfs {
useHost = true
}

install(Wasm)
install(JavaScript) {
npm {
enabled = true
modulesPath = System.getenv("PWD")
wasm = true
}
}
}.let { engine ->
engine.acquire {
build().use {
runTest {
block.invoke(it)
}
}
}
Unit
}

@DelicateElideApi
private fun executeGuest(
stdEngine: Boolean = true,
block: () -> String,
) {
if (stdEngine) this.executeGuest(bind = true) {
StringBuilder().apply {
if (ENABLE_POLYFILLS) appendLine(polyfillsContent)
append(block())
}.toString()
}.let {
it.doesNotFail()
it.returnValue().let { symbolValue ->
assertNotNull(symbolValue, "should get value from guest execution")
assertFalse(symbolValue.isNull, "guest value should not be `null`")
assertFalse(symbolValue.isBoolean && !symbolValue.asBoolean(), "value should not be `false`")
}
} else withFreshContext {
if (ENABLE_POLYFILLS) eval(polyfillsSrc)
eval(Source.create("js", block.invoke())).let { value ->
assertNotNull(value, "should get value from guest execution")
assertFalse(value.isNull, "value should not be `null`")
assertFalse(value.isBoolean && !value.asBoolean(), "value should not be `false`")
}
}
}

@OptIn(DelicateElideApi::class)
suspend fun SequenceScope<DynamicTest>.testFactory(globalName: String) {
if (globalName !in expectMissingGlobals) yield(
DynamicTest.dynamicTest(globalName) {
if (ENABLE_SUPPRESSIONS && globalName in allowMissingGlobals) {
Assumptions.abort<Unit>("Common Minimum API '$globalName' is known-missing")
}
executeGuest {
// language=JavaScript
"""
function pluck() {
return $globalName;
}
pluck();
"""
}
}
)
}

@DelicateElideApi
@TestFactory fun `minimum common api - interfaces`(): List<DynamicTest> = sequence<DynamicTest> {
val symbols = intrinsics.resolver().resolve(GraalVMGuest.JAVASCRIPT, internals = true).toList()
val mapped = mutableMapOf<Symbol, Any>()
val bindings = MutableIntrinsicBindings.Factory.wrap(mapped)
symbols.map { it as AbstractJsIntrinsic }.map {
it.install(bindings)
}
minimumCommonInterfaces.forEach { globalName ->
testFactory(globalName)
}
}.toList()

@DelicateElideApi
@TestFactory fun `minimum common api - globals`(): List<DynamicTest> = sequence<DynamicTest> {
val symbols = intrinsics.resolver().resolve(GraalVMGuest.JAVASCRIPT, internals = true).toList()
val mapped = mutableMapOf<Symbol, Any>()
val bindings = MutableIntrinsicBindings.Factory.wrap(mapped)
symbols.map { it as AbstractJsIntrinsic }.map {
it.install(bindings)
}
commonMinimumMethodsAndProperties.forEach { globalName ->
testFactory(globalName)
}
}.toList()
}

0 comments on commit f80dda9

Please sign in to comment.