diff --git a/.space/CODEOWNERS b/.space/CODEOWNERS index 7efd132c940..7f4e705924e 100644 --- a/.space/CODEOWNERS +++ b/.space/CODEOWNERS @@ -116,6 +116,7 @@ /compiler/testData/codegen/boxKlib/ "Kotlin JVM" /compiler/testData/codegen/boxModernJdk/ "Kotlin JVM" /compiler/testData/codegen/boxWasmJsInterop/ "Kotlin Wasm" +/compiler/testData/codegen/boxWasmWasi/ "Kotlin Wasm" /compiler/testData/codegen/bytecodeListing/ "Kotlin JVM" "Kotlin Compiler Core" /compiler/testData/codegen/bytecodeText/ "Kotlin JVM" "Kotlin Compiler Core" /compiler/testData/codegen/composeLike/ "Kotlin JVM" diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/TargetBackend.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/TargetBackend.kt index cc52f67307b..bd4f9cc0faa 100644 --- a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/TargetBackend.kt +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/TargetBackend.kt @@ -19,6 +19,7 @@ enum class TargetBackend( JS_IR(true, JS), JS_IR_ES6(true, JS_IR), WASM(true), + WASM_WASI(true), ANDROID(false, JVM), ANDROID_IR(true, JVM_IR), NATIVE(true), diff --git a/compiler/testData/codegen/boxWasmWasi/simpleWasi.kt b/compiler/testData/codegen/boxWasmWasi/simpleWasi.kt new file mode 100644 index 00000000000..a04c9c01960 --- /dev/null +++ b/compiler/testData/codegen/boxWasmWasi/simpleWasi.kt @@ -0,0 +1,3 @@ +fun box(): String { + return "OK" +} \ No newline at end of file diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt index d718d0dda48..205f29cfe36 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt @@ -344,10 +344,16 @@ class ClassicFrontendFacade( dependencyDescriptors: List, friendsDescriptors: List, ): AnalysisResult { + val suffix = when (configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS)) { + WasmTarget.JS -> "-js" + WasmTarget.WASI -> "-wasi" + else -> error("Unexpected wasi target") + } + val runtimeKlibsNames = listOfNotNull( - System.getProperty("kotlin.wasm.stdlib.path")!!, - System.getProperty("kotlin.wasm.kotlin.test.path")!! + System.getProperty("kotlin.wasm$suffix.stdlib.path")!!, + System.getProperty("kotlin.wasm$suffix.kotlin.test.path")!! ).map { File(it).absolutePath } diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFrontendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFrontendFacade.kt index 639d7e66431..67ac074b4de 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFrontendFacade.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFrontendFacade.kt @@ -30,6 +30,8 @@ import org.jetbrains.kotlin.fir.session.FirJvmSessionFactory import org.jetbrains.kotlin.fir.session.FirNativeSessionFactory import org.jetbrains.kotlin.fir.session.FirSessionConfigurator import org.jetbrains.kotlin.fir.session.environment.AbstractProjectEnvironment +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.WasmTarget import org.jetbrains.kotlin.load.kotlin.PackageAndMetadataPartProvider import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.platform.TargetPlatform @@ -419,7 +421,9 @@ open class FirFrontendFacade( friendDependencies(friendLibraries.map { it.toPath().toAbsolutePath() }) } targetPlatform.isWasm() -> { - val runtimeKlibsPaths = WasmEnvironmentConfigurator.getRuntimePathsForModule() + val runtimeKlibsPaths = WasmEnvironmentConfigurator.getRuntimePathsForModule( + configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) + ) val (transitiveLibraries, friendLibraries) = getTransitivesAndFriends(mainModule, testServices) dependencies(runtimeKlibsPaths.map { Paths.get(it).toAbsolutePath() }) dependencies(transitiveLibraries.map { it.toPath().toAbsolutePath() }) diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/TestFirWasmSessionFactory.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/TestFirWasmSessionFactory.kt index 0eb2633979a..f99ed2593e0 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/TestFirWasmSessionFactory.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/TestFirWasmSessionFactory.kt @@ -16,6 +16,8 @@ import org.jetbrains.kotlin.fir.session.FirSessionConfigurator import org.jetbrains.kotlin.fir.session.FirWasmSessionFactory import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.ir.backend.js.resolverLogger +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.WasmTarget import org.jetbrains.kotlin.library.metadata.resolver.KotlinResolvedLibrary import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.test.model.DependencyRelation @@ -36,7 +38,11 @@ object TestFirWasmSessionFactory { languageVersionSettings: LanguageVersionSettings, registerExtraComponents: ((FirSession) -> Unit), ): FirSession { - val resolvedLibraries = resolveLibraries(configuration, getAllWasmDependenciesPaths(module, testServices)) + val target = configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) + val resolvedLibraries = resolveLibraries( + configuration = configuration, + paths = getAllWasmDependenciesPaths(module, testServices, target) + ) return FirWasmSessionFactory.createLibrarySession( mainModuleName, @@ -72,16 +78,29 @@ fun resolveWasmLibraries( testServices: TestServices, configuration: CompilerConfiguration ): List { - return resolveLibraries(configuration, getAllWasmDependenciesPaths(module, testServices)) + val paths = getAllWasmDependenciesPaths( + module = module, + testServices = testServices, + target = configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) + ) + return resolveLibraries(configuration, paths) } -fun getAllWasmDependenciesPaths(module: TestModule, testServices: TestServices): List { - val (runtimeKlibsPaths, transitiveLibraries, friendLibraries) = getWasmDependencies(module, testServices) +fun getAllWasmDependenciesPaths( + module: TestModule, + testServices: TestServices, + target: WasmTarget, +): List { + val (runtimeKlibsPaths, transitiveLibraries, friendLibraries) = getWasmDependencies(module, testServices, target) return runtimeKlibsPaths + transitiveLibraries.map { it.path } + friendLibraries.map { it.path } } -fun getWasmDependencies(module: TestModule, testServices: TestServices): Triple, List, List> { - val runtimeKlibsPaths = WasmEnvironmentConfigurator.getRuntimePathsForModule() +fun getWasmDependencies( + module: TestModule, + testServices: TestServices, + target: WasmTarget, +): Triple, List, List> { + val runtimeKlibsPaths = WasmEnvironmentConfigurator.getRuntimePathsForModule(target) val transitiveLibraries = WasmEnvironmentConfigurator.getKlibDependencies(module, testServices, DependencyRelation.RegularDependency) val friendLibraries = WasmEnvironmentConfigurator.getKlibDependencies(module, testServices, DependencyRelation.FriendDependency) return Triple(runtimeKlibsPaths, transitiveLibraries, friendLibraries) diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/WasmEnvironmentConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/WasmEnvironmentConfigurator.kt index c4a6e66d80e..1068a7751d3 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/WasmEnvironmentConfigurator.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/WasmEnvironmentConfigurator.kt @@ -33,15 +33,34 @@ import org.jetbrains.kotlin.test.model.TestModule import org.jetbrains.kotlin.test.services.* import java.io.File -class WasmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { +class WasmEnvironmentConfiguratorJs(testServices: TestServices) : WasmEnvironmentConfigurator(testServices) { + override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) { + super.configureCompilerConfiguration(configuration, module) + configuration.put(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) + } +} + +class WasmEnvironmentConfiguratorWasi(testServices: TestServices) : WasmEnvironmentConfigurator(testServices) { + override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule) { + super.configureCompilerConfiguration(configuration, module) + configuration.put(JSConfigurationKeys.WASM_TARGET, WasmTarget.WASI) + } +} + +abstract class WasmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { override val directiveContainers: List get() = listOf(WasmEnvironmentConfigurationDirectives) companion object { private const val OUTPUT_KLIB_DIR_NAME = "outputKlibDir" - fun getRuntimePathsForModule(): List { - return listOf(System.getProperty("kotlin.wasm.stdlib.path")!!, System.getProperty("kotlin.wasm.kotlin.test.path")!!) + fun getRuntimePathsForModule(target: WasmTarget): List { + val suffix = when (target) { + WasmTarget.JS -> "-js" + WasmTarget.WASI -> "-wasi" + else -> error("Unexpected wasi target") + } + return listOf(System.getProperty("kotlin.wasm$suffix.stdlib.path")!!, System.getProperty("kotlin.wasm$suffix.kotlin.test.path")!!) } fun getKlibDependencies(module: TestModule, testServices: TestServices, kind: DependencyRelation): List { diff --git a/js/js.translator/testData/_commonFiles/testUtils.kt b/js/js.translator/testData/_commonFiles/testUtils.kt index 41983f1014c..fefec85f7f6 100644 --- a/js/js.translator/testData/_commonFiles/testUtils.kt +++ b/js/js.translator/testData/_commonFiles/testUtils.kt @@ -5,5 +5,4 @@ private external fun evalToBoolean(code: String): Boolean fun isLegacyBackend(): Boolean = // Using eval to prevent DCE from thinking that following code depends on Kotlin module. - evalToBoolean("(typeof Kotlin != \"undefined\" && typeof Kotlin.kotlin != \"undefined\")") - + evalToBoolean("(typeof Kotlin != \"undefined\" && typeof Kotlin.kotlin != \"undefined\")") \ No newline at end of file diff --git a/libraries/stdlib/test/testUtils.kt b/libraries/stdlib/test/testUtils.kt index 91b4994201b..91394cacb11 100644 --- a/libraries/stdlib/test/testUtils.kt +++ b/libraries/stdlib/test/testUtils.kt @@ -14,7 +14,8 @@ public enum class TestPlatform { Jvm, Js, Native, - Wasm; + WasmJs, + WasmWasi; companion object } diff --git a/libraries/stdlib/test/time/DurationTest.kt b/libraries/stdlib/test/time/DurationTest.kt index 857209ea1f8..bebccf6766c 100644 --- a/libraries/stdlib/test/time/DurationTest.kt +++ b/libraries/stdlib/test/time/DurationTest.kt @@ -6,6 +6,8 @@ @file:Suppress("INVISIBLE_MEMBER") package test.time +import test.TestPlatform +import test.current import test.numbers.assertAlmostEquals import kotlin.math.nextDown import kotlin.math.pow @@ -609,6 +611,8 @@ class DurationTest { @Test fun parseAndFormatInUnits() { + if (TestPlatform.current == TestPlatform.WasmWasi) return + var d = 1.days + 15.hours + 31.minutes + 45.seconds + 678.milliseconds + 920.microseconds + 516.34.nanoseconds diff --git a/libraries/stdlib/test/time/TimeMarkTest.kt b/libraries/stdlib/test/time/TimeMarkTest.kt index 541276eb0f4..660d810e4bc 100644 --- a/libraries/stdlib/test/time/TimeMarkTest.kt +++ b/libraries/stdlib/test/time/TimeMarkTest.kt @@ -5,6 +5,8 @@ package test.time +import test.TestPlatform +import test.current import kotlin.math.sign import kotlin.test.* import kotlin.time.* @@ -367,6 +369,8 @@ class TimeMarkTest { @Test fun defaultTimeMarkAdjustmentBig() { + if (TestPlatform.current == TestPlatform.WasmWasi) return + testAdjustmentBig(TimeSource.Monotonic) // do the same with specialized methods @@ -397,6 +401,8 @@ class TimeMarkTest { @Test fun defaultTimeMarkAdjustmentInfinite() { + if (TestPlatform.current == TestPlatform.WasmWasi) return + testAdjustmentInfinite(TimeSource.Monotonic) // do the same with specialized methods diff --git a/libraries/stdlib/wasm/js/build.gradle.kts b/libraries/stdlib/wasm/js/build.gradle.kts index a48bd502153..370e4df9d03 100644 --- a/libraries/stdlib/wasm/js/build.gradle.kts +++ b/libraries/stdlib/wasm/js/build.gradle.kts @@ -18,7 +18,7 @@ configureWasmStdLib( wasmTargetParameter = "wasm-js", wasmTargetAttribute = KotlinWasmTargetAttribute.js, targetDependentSources = targetDependentSources, - targetDependentTestSources = listOf("$rootDir/libraries/stdlib/wasm/testJs/"), + targetDependentTestSources = listOf("$rootDir/libraries/stdlib/wasm/js/test/"), kotlinTestDependencyName = ":kotlin-test:kotlin-test-wasm-js" ) { extensionBody -> kotlin(extensionBody) diff --git a/libraries/stdlib/wasm/testJs/js/AsyncTest.kt b/libraries/stdlib/wasm/js/test/js/AsyncTest.kt similarity index 100% rename from libraries/stdlib/wasm/testJs/js/AsyncTest.kt rename to libraries/stdlib/wasm/js/test/js/AsyncTest.kt diff --git a/libraries/stdlib/wasm/js/test/testUtils.kt b/libraries/stdlib/wasm/js/test/testUtils.kt new file mode 100644 index 00000000000..e31850bdec4 --- /dev/null +++ b/libraries/stdlib/wasm/js/test/testUtils.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test + +public actual val TestPlatform.Companion.current: TestPlatform get() = TestPlatform.WasmJs \ No newline at end of file diff --git a/libraries/stdlib/wasm/testJs/unsafe/MemoryAllocationJsTest.kt b/libraries/stdlib/wasm/js/test/unsafe/MemoryAllocationJsTest.kt similarity index 100% rename from libraries/stdlib/wasm/testJs/unsafe/MemoryAllocationJsTest.kt rename to libraries/stdlib/wasm/js/test/unsafe/MemoryAllocationJsTest.kt diff --git a/libraries/stdlib/wasm/test/testUtils.kt b/libraries/stdlib/wasm/test/testUtils.kt index 493d1e7ad81..7f3fe4b8779 100644 --- a/libraries/stdlib/wasm/test/testUtils.kt +++ b/libraries/stdlib/wasm/test/testUtils.kt @@ -12,8 +12,6 @@ public actual fun assertTypeEquals(expected: Any?, actual: Any?) { assertEquals(expected?.let { it::class }, actual?.let { it::class }) } -public actual val TestPlatform.Companion.current: TestPlatform get() = TestPlatform.Wasm - // TODO: See KT-24975 public actual val isFloat32RangeEnforced: Boolean = false diff --git a/libraries/stdlib/wasm/wasi/build.gradle.kts b/libraries/stdlib/wasm/wasi/build.gradle.kts index 570e5022d4e..28aaab76131 100644 --- a/libraries/stdlib/wasm/wasi/build.gradle.kts +++ b/libraries/stdlib/wasm/wasi/build.gradle.kts @@ -16,7 +16,7 @@ configureWasmStdLib( wasmTargetParameter = "wasm-wasi", wasmTargetAttribute = KotlinWasmTargetAttribute.wasi, targetDependentSources = targetDependentSources, - targetDependentTestSources = emptyList(), + targetDependentTestSources = listOf("$rootDir/libraries/stdlib/wasm/wasi/test/"), kotlinTestDependencyName = ":kotlin-test:kotlin-test-wasm-wasi" ) { extensionBody -> kotlin(extensionBody) diff --git a/libraries/stdlib/wasm/wasi/test/testWasi.kt b/libraries/stdlib/wasm/wasi/test/testWasi.kt new file mode 100644 index 00000000000..ae4d35f4ff0 --- /dev/null +++ b/libraries/stdlib/wasm/wasi/test/testWasi.kt @@ -0,0 +1,8 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package test + +public actual val TestPlatform.Companion.current: TestPlatform get() = TestPlatform.WasmWasi \ No newline at end of file diff --git a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupV8.kt b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupV8.kt index b3d67c53e1b..34a7507cfd9 100644 --- a/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupV8.kt +++ b/repo/gradle-build-conventions/buildsrc-compat/src/main/kotlin/setupV8.kt @@ -6,6 +6,8 @@ import org.gradle.api.Project import org.gradle.api.tasks.testing.Test import org.jetbrains.kotlin.gradle.targets.js.d8.D8RootExtension import org.jetbrains.kotlin.gradle.targets.js.d8.D8RootPlugin +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin private object V8Utils { lateinit var d8Plugin: D8RootExtension @@ -30,3 +32,27 @@ fun Test.setupV8() { } } + +private object NodeJsUtils { + lateinit var nodeJsPlugin: NodeJsRootExtension + + fun useNodeJsPlugin(project: Project) { + nodeJsPlugin = NodeJsRootPlugin.apply(project.rootProject) + nodeJsPlugin.nodeVersion = project.nodejsVersion + } +} + +fun Project.useNodeJsPlugin() { + NodeJsUtils.useNodeJsPlugin(this) +} + +fun Test.setupNodeJs() { + dependsOn(NodeJsUtils.nodeJsPlugin.nodeJsSetupTaskProvider) + val nodeJsExecutablePath = project.provider { + NodeJsUtils.nodeJsPlugin.requireConfigured().nodeExecutable + } + doFirst { + systemProperty("javascript.engine.path.NodeJs", nodeJsExecutablePath.get()) + } +} + diff --git a/wasm/wasm.tests/_commonFiles/arrayAsserts.kt b/wasm/wasm.tests/_commonFiles/arrayAsserts.kt new file mode 100644 index 00000000000..dcd1b78faf6 --- /dev/null +++ b/wasm/wasm.tests/_commonFiles/arrayAsserts.kt @@ -0,0 +1,25 @@ +package kotlin + +fun assertArrayEquals(expected: Array, actual: Array, message: String? = null) { + if (!arraysEqual(expected, actual)) { + val msg = if (message == null) "" else ", message = '$message'" + fail("Unexpected array: expected = '$expected', actual = '$actual'$msg") + } +} + +private fun arraysEqual(first: Array, second: Array): Boolean { + if (first === second) return true + if (first.size != second.size) return false + for (index in 0..first.size - 1) { + if (!equal(first[index], second[index])) return false + } + return true +} + +private fun equal(first: Any?, second: Any?) = + if (first is Array<*> && second is Array<*>) { + arraysEqual(first, second) + } + else { + first == second + } \ No newline at end of file diff --git a/wasm/wasm.tests/_commonFiles/asserts.kt b/wasm/wasm.tests/_commonFiles/asserts.kt new file mode 100644 index 00000000000..59a50e3507b --- /dev/null +++ b/wasm/wasm.tests/_commonFiles/asserts.kt @@ -0,0 +1,45 @@ +package kotlin +// This file should be excluded from tests using StdLib, as these methods conflict with corresponding methods from kotlin.test +// see StdLibTestBase.removeAdHocAssertions + +fun assertEquals(expected: T, actual: T, message: String? = null) { + if (expected != actual) { + val msg = if (message == null) "" else ", message = '$message'" + fail("Unexpected value: expected = '$expected', actual = '$actual'$msg") + } +} + +fun assertNotEquals(illegal: T, actual: T, message: String? = null) { + if (illegal == actual) { + val msg = if (message == null) "" else ", message = '$message'" + fail("Illegal value: illegal = '$illegal', actual = '$actual'$msg") + } +} + +fun assertSame(expected: T, actual: T, message: String? = null) { + if (expected !== actual) { + val msg = if (message == null) "" else ", message = '$message'" + fail("Expected same instances: expected = '$expected', actual = '$actual'$msg") + } +} + +fun assertTrue(actual: Boolean, message: String? = null) = assertEquals(true, actual, message) + +fun assertFalse(actual: Boolean, message: String? = null) = assertEquals(false, actual, message) + +fun testTrue(f: () -> Boolean) { + assertTrue(f(), f.toString()) +} + +fun testFalse(f: () -> Boolean) { + assertFalse(f(), f.toString()) +} + +fun assertFails(block: () -> Unit): Throwable { + try { + block() + } catch (t: Throwable) { + return t + } + fail("Expected an exception to be thrown, but was completed successfully.") +} \ No newline at end of file diff --git a/wasm/wasm.tests/_commonFiles/fail.kt b/wasm/wasm.tests/_commonFiles/fail.kt new file mode 100644 index 00000000000..481c56d3c24 --- /dev/null +++ b/wasm/wasm.tests/_commonFiles/fail.kt @@ -0,0 +1,7 @@ +package kotlin +// This file should be excluded from tests using StdLib, as these methods conflict with corresponding methods from kotlin.test +// see StdLibTestBase.removeAdHocAssertions + +fun fail(message: String? = null): Nothing { + throw Throwable(message) +} \ No newline at end of file diff --git a/wasm/wasm.tests/build.gradle.kts b/wasm/wasm.tests/build.gradle.kts index 80c9c70372e..d381f45e2a4 100644 --- a/wasm/wasm.tests/build.gradle.kts +++ b/wasm/wasm.tests/build.gradle.kts @@ -76,6 +76,7 @@ dependencies { val generationRoot = projectDir.resolve("tests-gen") useD8Plugin() +useNodeJsPlugin() optInToExperimentalCompilerApi() sourceSets { @@ -86,11 +87,11 @@ sourceSets { } } -fun Test.setupWasmStdlib() { - dependsOn(":kotlin-stdlib-wasm-js:compileKotlinWasm") - systemProperty("kotlin.wasm.stdlib.path", "libraries/stdlib/wasm/js/build/classes/kotlin/wasm/main") - dependsOn(":kotlin-test:kotlin-test-wasm-js:compileKotlinWasm") - systemProperty("kotlin.wasm.kotlin.test.path", "libraries/kotlin.test/wasm/js/build/classes/kotlin/wasm/main") +fun Test.setupWasmStdlib(target: String) { + dependsOn(":kotlin-stdlib-wasm-$target:compileKotlinWasm") + systemProperty("kotlin.wasm-$target.stdlib.path", "libraries/stdlib/wasm/$target/build/classes/kotlin/wasm/main") + dependsOn(":kotlin-test:kotlin-test-wasm-$target:compileKotlinWasm") + systemProperty("kotlin.wasm-$target.kotlin.test.path", "libraries/kotlin.test/wasm/$target/build/classes/kotlin/wasm/main") } fun Test.setupGradlePropertiesForwarding() { @@ -144,9 +145,11 @@ fun Project.wasmProjectTest( ) { workingDir = rootDir setupV8() + setupNodeJs() setupSpiderMonkey() useJUnitPlatform() - setupWasmStdlib() + setupWasmStdlib("js") + setupWasmStdlib("wasi") setupGradlePropertiesForwarding() systemProperty("kotlin.wasm.test.root.out.dir", "$buildDir/") body() diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/generators/tests/GenerateWasmTests.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/generators/tests/GenerateWasmTests.kt index e43a3b1e057..af133d7c490 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/generators/tests/GenerateWasmTests.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/generators/tests/GenerateWasmTests.kt @@ -80,6 +80,10 @@ fun main(args: Array) { testClass { model("codegen/boxWasmJsInterop") } + + testClass { + model("codegen/boxWasmWasi") + } } } } \ No newline at end of file diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractFirWasmTest.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractFirWasmTest.kt index a258b9f905b..4c0513c5e8a 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractFirWasmTest.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractFirWasmTest.kt @@ -17,8 +17,11 @@ import org.jetbrains.kotlin.test.frontend.fir.* import org.jetbrains.kotlin.test.frontend.fir.handlers.* import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.runners.codegen.commonFirHandlersForCodegenTest +import org.jetbrains.kotlin.test.services.EnvironmentConfigurator +import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfiguratorJs import org.jetbrains.kotlin.wasm.test.converters.FirWasmKlibBackendFacade import org.jetbrains.kotlin.wasm.test.converters.WasmBackendFacade +import org.jetbrains.kotlin.wasm.test.handlers.WasmBoxRunner open class AbstractFirWasmTest( @@ -39,6 +42,12 @@ open class AbstractFirWasmTest( override val afterBackendFacade: Constructor> get() = ::WasmBackendFacade + override val wasmBoxTestRunner: Constructor> + get() = ::WasmBoxRunner + + override val wasmEnvironmentConfigurator: Constructor + get() = ::WasmEnvironmentConfiguratorJs + override fun configure(builder: TestConfigurationBuilder) { super.configure(builder) with(builder) { diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractK1WasmTest.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractK1WasmTest.kt index 96411d7a099..1878b8bc83c 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractK1WasmTest.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractK1WasmTest.kt @@ -12,8 +12,11 @@ import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontend2IrConverter import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendFacade import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.EnvironmentConfigurator +import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfiguratorJs import org.jetbrains.kotlin.wasm.test.converters.FirWasmKlibBackendFacade import org.jetbrains.kotlin.wasm.test.converters.WasmBackendFacade +import org.jetbrains.kotlin.wasm.test.handlers.WasmBoxRunner abstract class AbstractK1WasmTest( pathToTestDir: String, @@ -32,6 +35,12 @@ abstract class AbstractK1WasmTest( override val afterBackendFacade: Constructor> get() = ::WasmBackendFacade + + override val wasmBoxTestRunner: Constructor> + get() = ::WasmBoxRunner + + override val wasmEnvironmentConfigurator: Constructor + get() = ::WasmEnvironmentConfiguratorJs } open class AbstractK1WasmCodegenBoxTest : AbstractK1WasmTest( diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractK1WasmWasiTest.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractK1WasmWasiTest.kt new file mode 100644 index 00000000000..c22cf0e3b8c --- /dev/null +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractK1WasmWasiTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.wasm.test + +import org.jetbrains.kotlin.test.Constructor +import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.test.backend.ir.IrBackendInput +import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder +import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontend2IrConverter +import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendFacade +import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.AdditionalSourceProvider +import org.jetbrains.kotlin.test.services.EnvironmentConfigurator +import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfiguratorWasi +import org.jetbrains.kotlin.wasm.test.converters.FirWasmKlibBackendFacade +import org.jetbrains.kotlin.wasm.test.converters.WasmBackendFacade +import org.jetbrains.kotlin.wasm.test.handlers.WasiBoxRunner + +abstract class AbstractK1WasmWasiTest( + pathToTestDir: String, + testGroupOutputDirPrefix: String, +) : AbstractWasmBlackBoxCodegenTestBase( + FrontendKinds.ClassicFrontend, TargetBackend.WASM, pathToTestDir, testGroupOutputDirPrefix +) { + override val frontendFacade: Constructor> + get() = ::ClassicFrontendFacade + + override val frontendToBackendConverter: Constructor> + get() = ::ClassicFrontend2IrConverter + + override val backendFacade: Constructor> + get() = ::FirWasmKlibBackendFacade + + override val afterBackendFacade: Constructor> + get() = ::WasmBackendFacade + + override val wasmBoxTestRunner: Constructor> + get() = ::WasiBoxRunner + + override val wasmEnvironmentConfigurator: Constructor + get() = ::WasmEnvironmentConfiguratorWasi + + override val additionalSourceProvider: Constructor? + get() = ::WasmWasiBoxTestHelperSourceProvider +} + +open class AbstractK1WasmWasiCodegenBoxTest : AbstractK1WasmWasiTest( + "compiler/testData/codegen/boxWasmWasi/", + "codegen/k1WasmWasiBox" +) diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractWasmBlackBoxCodegenTestBase.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractWasmBlackBoxCodegenTestBase.kt index 7d1dfecfff6..32b724f1c60 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractWasmBlackBoxCodegenTestBase.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/AbstractWasmBlackBoxCodegenTestBase.kt @@ -21,10 +21,11 @@ import org.jetbrains.kotlin.test.model.* import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerWithTargetBackendTest import org.jetbrains.kotlin.test.runners.codegen.actualizersAndPluginsFacadeStepIfNeeded import org.jetbrains.kotlin.test.runners.codegen.commonClassicFrontendHandlersForCodegenTest +import org.jetbrains.kotlin.test.services.AdditionalSourceProvider +import org.jetbrains.kotlin.test.services.EnvironmentConfigurator import org.jetbrains.kotlin.test.services.LibraryProvider import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfigurator import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider -import org.jetbrains.kotlin.wasm.test.handlers.WasmBoxRunner abstract class AbstractWasmBlackBoxCodegenTestBase, I : ResultingArtifact.BackendInput, A : ResultingArtifact.Binary>( private val targetFrontend: FrontendKind, @@ -36,6 +37,9 @@ abstract class AbstractWasmBlackBoxCodegenTestBase> abstract val backendFacade: Constructor> abstract val afterBackendFacade: Constructor> + abstract val wasmBoxTestRunner: Constructor> + abstract val wasmEnvironmentConfigurator: Constructor + open val additionalSourceProvider: Constructor? = null override fun TestConfigurationBuilder.configuration() { globalDefaults { @@ -63,7 +67,7 @@ abstract class AbstractWasmBlackBoxCodegenTestBase { + val boxTestRunFile = File("wasm/wasm.tests/wasiBoxTestRun.kt") + return listOf(boxTestRunFile.toTestFile()) + } +} + class WasmAdditionalSourceProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) { override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List { if (JsEnvironmentConfigurationDirectives.NO_COMMON_FILES in module.directives) return emptyList() @@ -24,7 +31,7 @@ class WasmAdditionalSourceProvider(testServices: TestServices) : AdditionalSourc companion object { private const val COMMON_FILES_NAME = "_common" private const val COMMON_FILES_DIR = "_commonFiles/" - private const val COMMON_FILES_DIR_PATH = "js/js.translator/testData/$COMMON_FILES_DIR" + private const val COMMON_FILES_DIR_PATH = "wasm/wasm.tests/$COMMON_FILES_DIR" private fun getFilesInDirectoryByExtension(directory: String, extension: String): List { val dir = File(directory) diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/FirWasmKlibBackendFacade.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/FirWasmKlibBackendFacade.kt index b8c5286a9f5..9d2a425ab1d 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/FirWasmKlibBackendFacade.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/FirWasmKlibBackendFacade.kt @@ -14,6 +14,8 @@ import org.jetbrains.kotlin.ir.backend.js.JsFactories import org.jetbrains.kotlin.ir.backend.js.resolverLogger import org.jetbrains.kotlin.ir.backend.js.serializeModuleIntoKlib import org.jetbrains.kotlin.ir.util.IrMessageLogger +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.WasmTarget import org.jetbrains.kotlin.library.KotlinAbiVersion import org.jetbrains.kotlin.storage.LockBasedStorageManager import org.jetbrains.kotlin.test.backend.ir.IrBackendFacade @@ -52,7 +54,8 @@ class FirWasmKlibBackendFacade( val outputFile = WasmEnvironmentConfigurator.getWasmKlibArtifactPath(testServices, module.name) // TODO: consider avoiding repeated libraries resolution - val libraries = resolveLibraries(configuration, getAllWasmDependenciesPaths(module, testServices)) + val target = configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS) + val libraries = resolveLibraries(configuration, getAllWasmDependenciesPaths(module, testServices, target)) if (firstTimeCompilation) { serializeModuleIntoKlib( @@ -77,7 +80,7 @@ class FirWasmKlibBackendFacade( // TODO: consider avoiding repeated libraries resolution val lib = CommonKLibResolver.resolve( - getAllWasmDependenciesPaths(module, testServices) + listOf(outputFile), + getAllWasmDependenciesPaths(module, testServices, target) + listOf(outputFile), configuration.resolverLogger ).getFullResolvedList().last().library diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt index 41a81e99c43..9ec9759dbc9 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/converters/WasmBackendFacade.kt @@ -20,6 +20,8 @@ import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageConfig import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageLogLevel import org.jetbrains.kotlin.ir.linkage.partial.PartialLinkageMode import org.jetbrains.kotlin.ir.linkage.partial.setupPartialLinkageConfig +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.WasmTarget import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.test.DebugMode @@ -62,9 +64,15 @@ class WasmBackendFacade( PhaseConfig(wasmPhases) } + val suffix = when (configuration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS)) { + WasmTarget.JS -> "-js" + WasmTarget.WASI -> "-wasi" + else -> error("Unexpected wasi target") + } + val libraries = listOf( - System.getProperty("kotlin.wasm.stdlib.path")!!, - System.getProperty("kotlin.wasm.kotlin.test.path")!! + System.getProperty("kotlin.wasm$suffix.stdlib.path")!!, + System.getProperty("kotlin.wasm$suffix.kotlin.test.path")!! ) + WasmEnvironmentConfigurator.getAllRecursiveLibrariesFor(module, testServices).map { it.key.libraryFile.canonicalPath } val friendLibraries = emptyList() diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/diagnostics/AbstractDiagnosticsWasmTest.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/diagnostics/AbstractDiagnosticsWasmTest.kt index b64b2646f3e..4c2f218a8e1 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/diagnostics/AbstractDiagnosticsWasmTest.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/diagnostics/AbstractDiagnosticsWasmTest.kt @@ -18,6 +18,7 @@ import org.jetbrains.kotlin.test.runners.AbstractKotlinCompilerTest import org.jetbrains.kotlin.test.services.LibraryProvider import org.jetbrains.kotlin.test.services.configuration.CommonEnvironmentConfigurator import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfigurator +import org.jetbrains.kotlin.test.services.configuration.WasmEnvironmentConfiguratorJs import org.jetbrains.kotlin.test.services.sourceProviders.AdditionalDiagnosticsSourceFilesProvider import org.jetbrains.kotlin.test.services.sourceProviders.CoroutineHelpersSourceFilesProvider @@ -37,7 +38,7 @@ abstract class AbstractDiagnosticsWasmTest : AbstractKotlinCompilerTest() { useConfigurators( ::CommonEnvironmentConfigurator, - ::WasmEnvironmentConfigurator, + ::WasmEnvironmentConfiguratorJs, ) useMetaInfoProcessors(::OldNewInferenceMetaInfoProcessor) diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasiBoxRunner.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasiBoxRunner.kt new file mode 100644 index 00000000000..10e7b63963f --- /dev/null +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasiBoxRunner.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.wasm.test.handlers + +import org.jetbrains.kotlin.backend.wasm.WasmCompilerResult +import org.jetbrains.kotlin.backend.wasm.writeCompilationResult +import org.jetbrains.kotlin.js.JavaScript +import org.jetbrains.kotlin.test.DebugMode +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives +import org.jetbrains.kotlin.test.directives.WasmEnvironmentConfigurationDirectives.RUN_UNIT_TESTS +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.moduleStructure +import org.jetbrains.kotlin.wasm.test.tools.WasmVM +import java.io.File + +class WasiBoxRunner( + testServices: TestServices +) : AbstractWasmArtifactsCollector(testServices) { + override fun processAfterAllModules(someAssertionWasFailed: Boolean) { + if (!someAssertionWasFailed) { + runWasmCode() + } + } + + private fun runWasmCode() { + val artifacts = modulesToArtifact.values.single() + val baseFileName = "index" + val outputDirBase = testServices.getWasmTestOutputDirectory() + + val originalFile = testServices.moduleStructure.originalTestDataFiles.first() + + val debugMode = DebugMode.fromSystemProperty("kotlin.wasm.debugMode") + val startUnitTests = RUN_UNIT_TESTS in testServices.moduleStructure.allDirectives + + val testWasiQuiet = """ + let boxTestPassed = false; + try { + let jsModule = await import('./index.mjs'); + let wasmExports = jsModule.default; + ${if (startUnitTests) "wasmExports.startUnitTests();" else ""} + boxTestPassed = wasmExports.runBoxTest(); + } catch(e) { + console.log('Failed with exception!'); + console.log(e); + } + + if (!boxTestPassed) + process.exit(1); + """.trimIndent() + + val testWasiVerbose = testWasiQuiet + """ + + + console.log('test passed'); + """.trimIndent() + + val testWasi = if (debugMode >= DebugMode.DEBUG) testWasiVerbose else testWasiQuiet + + fun writeToFilesAndRunTest(mode: String, res: WasmCompilerResult) { + val dir = File(outputDirBase, mode) + dir.mkdirs() + + writeCompilationResult(res, dir, baseFileName) + File(dir, "test.mjs").writeText(testWasi) + + if (debugMode >= DebugMode.DEBUG) { + val path = dir.absolutePath + println(" ------ $mode Wat file://$path/index.wat") + println(" ------ $mode Wasm file://$path/index.wasm") + println(" ------ $mode JS file://$path/index.mjs") + println(" ------ $mode Test file://$path/test.mjs") + } + + val testFileText = originalFile.readText() + val failsIn: List = InTextDirectivesUtils.findListWithPrefixes(testFileText, "// WASM_FAILS_IN: ") + + val exception = WasmVM.NodeJs.runWithCathedExceptions( + debugMode = debugMode, + disableExceptions = false, + failsIn = failsIn, + entryMjs = "test.mjs", + jsFilePaths = emptyList(), + workingDirectory = dir + ) + + if (exception != null) { + throw exception + } + + if (mode == "dce") { + checkExpectedOutputSize(debugMode, testFileText, dir) + } + } + + writeToFilesAndRunTest("dev", artifacts.compilerResult) + writeToFilesAndRunTest("dce", artifacts.compilerResultWithDCE) + } +} \ No newline at end of file diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt index 99098a60d68..5dc807aef00 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/handlers/WasmBoxRunner.kt @@ -92,48 +92,13 @@ class WasmBoxRunner( """.trimIndent() val testJsVerbose = testJsQuiet + """ + + console.log('test passed'); """.trimIndent() val testJs = if (debugMode >= DebugMode.DEBUG) testJsVerbose else testJsQuiet - fun checkExpectedOutputSize(testFileContent: String, testDir: File) { - val expectedSizes = - InTextDirectivesUtils.findListWithPrefixes(testFileContent, "// WASM_DCE_EXPECTED_OUTPUT_SIZE: ") - .map { - val i = it.indexOf(' ') - val extension = it.substring(0, i) - val size = it.substring(i + 1) - extension.trim().lowercase() to size.filter(Char::isDigit).toInt() - } - - val filesByExtension = testDir.listFiles()?.groupBy { it.extension }.orEmpty() - - val errors = expectedSizes.mapNotNull { (extension, expectedSize) -> - val totalSize = filesByExtension[extension].orEmpty().sumOf { it.length() } - - val thresholdPercent = 1 - val thresholdInBytes = expectedSize * thresholdPercent / 100 - - val expectedMinSize = expectedSize - thresholdInBytes - val expectedMaxSize = expectedSize + thresholdInBytes - - val diff = totalSize - expectedSize - - val message = "Total size of $extension files is $totalSize," + - " but expected $expectedSize ∓ $thresholdInBytes [$expectedMinSize .. $expectedMaxSize]." + - " Diff: $diff (${diff * 100 / expectedSize}%)" - - if (debugMode >= DebugMode.DEBUG) { - println(" ------ $message") - } - - if (totalSize !in expectedMinSize..expectedMaxSize) message else null - } - - if (errors.isNotEmpty()) throw AssertionError(errors.joinToString("\n")) - } - fun writeToFilesAndRunTest(mode: String, res: WasmCompilerResult) { val dir = File(outputDirBase, mode) dir.mkdirs() @@ -194,26 +159,15 @@ class WasmBoxRunner( val disableExceptions = DISABLE_WASM_EXCEPTION_HANDLING in testServices.moduleStructure.allDirectives - val exceptions = listOf(WasmVM.V8, WasmVM.SpiderMonkey).mapNotNull map@{ vm -> - try { - if (debugMode >= DebugMode.DEBUG) { - println(" ------ Run in ${vm.name}" + if (vm.shortName in failsIn) " (expected to fail)" else "") - } - vm.run( - "./${entryMjs}", - jsFilePaths, - workingDirectory = dir, - disableExceptionHandlingIfPossible = disableExceptions - ) - if (vm.shortName in failsIn) { - return@map AssertionError("The test expected to fail in ${vm.name}. Please update the testdata.") - } - } catch (e: Throwable) { - if (vm.shortName !in failsIn) { - return@map e - } - } - null + val exceptions = listOf(WasmVM.V8, WasmVM.SpiderMonkey).mapNotNull { vm -> + vm.runWithCathedExceptions( + debugMode = debugMode, + disableExceptions = disableExceptions, + failsIn = failsIn, + entryMjs = entryMjs, + jsFilePaths = jsFilePaths, + workingDirectory = dir, + ) } when (exceptions.size) { @@ -229,7 +183,7 @@ class WasmBoxRunner( } if (mode == "dce") { - checkExpectedOutputSize(testFileText, dir) + checkExpectedOutputSize(debugMode, testFileText, dir) } } @@ -240,6 +194,35 @@ class WasmBoxRunner( private class AdditionalFile(val name: String, val content: String) } +internal fun WasmVM.runWithCathedExceptions( + debugMode: DebugMode, + disableExceptions: Boolean, + failsIn: List, + entryMjs: String?, + jsFilePaths: List, + workingDirectory: File, +): Throwable? { + try { + if (debugMode >= DebugMode.DEBUG) { + println(" ------ Run in ${name}" + if (shortName in failsIn) " (expected to fail)" else "") + } + run( + "./${entryMjs}", + jsFilePaths, + workingDirectory = workingDirectory, + disableExceptionHandlingIfPossible = disableExceptions, + ) + if (shortName in failsIn) { + return AssertionError("The test expected to fail in ${name}. Please update the testdata.") + } + } catch (e: Throwable) { + if (shortName !in failsIn) { + return e + } + } + return null +} + fun TestServices.getWasmTestOutputDirectory(): File { val originalFile = moduleStructure.originalTestDataFiles.first() val allDirectives = moduleStructure.allDirectives @@ -256,4 +239,41 @@ fun TestServices.getWasmTestOutputDirectory(): File { .toList().asReversed() .fold(testGroupOutputDir, ::File) .let { File(it, originalFile.nameWithoutExtension) } -} \ No newline at end of file +} + +fun checkExpectedOutputSize(debugMode: DebugMode, testFileContent: String, testDir: File) { + val expectedSizes = + InTextDirectivesUtils.findListWithPrefixes(testFileContent, "// WASM_DCE_EXPECTED_OUTPUT_SIZE: ") + .map { + val i = it.indexOf(' ') + val extension = it.substring(0, i) + val size = it.substring(i + 1) + extension.trim().lowercase() to size.filter(Char::isDigit).toInt() + } + + val filesByExtension = testDir.listFiles()?.groupBy { it.extension }.orEmpty() + + val errors = expectedSizes.mapNotNull { (extension, expectedSize) -> + val totalSize = filesByExtension[extension].orEmpty().sumOf { it.length() } + + val thresholdPercent = 1 + val thresholdInBytes = expectedSize * thresholdPercent / 100 + + val expectedMinSize = expectedSize - thresholdInBytes + val expectedMaxSize = expectedSize + thresholdInBytes + + val diff = totalSize - expectedSize + + val message = "Total size of $extension files is $totalSize," + + " but expected $expectedSize ∓ $thresholdInBytes [$expectedMinSize .. $expectedMaxSize]." + + " Diff: $diff (${diff * 100 / expectedSize}%)" + + if (debugMode >= DebugMode.DEBUG) { + println(" ------ $message") + } + + if (totalSize !in expectedMinSize..expectedMaxSize) message else null + } + + if (errors.isNotEmpty()) throw AssertionError(errors.joinToString("\n")) +} diff --git a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt index c629ee3f16f..d7904ae3600 100644 --- a/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt +++ b/wasm/wasm.tests/test/org/jetbrains/kotlin/wasm/test/tools/WasmVM.kt @@ -46,6 +46,17 @@ internal sealed class WasmVM(val shortName: String) { ) } } + + object NodeJs : WasmVM("NodeJs") { + override fun run(entryMjs: String, jsFiles: List, workingDirectory: File?, disableExceptionHandlingIfPossible: Boolean) { + tool.run( + "--experimental-wasm-gc", + *jsFiles.flatMap { listOf("-f", it) }.toTypedArray(), + entryMjs, + workingDirectory = workingDirectory + ) + } + } } internal class ExternalTool(val path: String) { diff --git a/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmWasiCodegenBoxTestGenerated.java b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmWasiCodegenBoxTestGenerated.java new file mode 100644 index 00000000000..1d407a434e3 --- /dev/null +++ b/wasm/wasm.tests/tests-gen/org/jetbrains/kotlin/wasm/test/K1WasmWasiCodegenBoxTestGenerated.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.wasm.test; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.TargetBackend; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.GenerateWasmTestsKt}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("compiler/testData/codegen/boxWasmWasi") +@TestDataPath("$PROJECT_ROOT") +public class K1WasmWasiCodegenBoxTestGenerated extends AbstractK1WasmWasiCodegenBoxTest { + @Test + public void testAllFilesPresentInBoxWasmWasi() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/boxWasmWasi"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.WASM, true); + } + + @Test + @TestMetadata("simpleWasi.kt") + public void testSimpleWasi() throws Exception { + runTest("compiler/testData/codegen/boxWasmWasi/simpleWasi.kt"); + } +} diff --git a/wasm/wasm.tests/wasiBoxTestRun.kt b/wasm/wasm.tests/wasiBoxTestRun.kt new file mode 100644 index 00000000000..b7346aae232 --- /dev/null +++ b/wasm/wasm.tests/wasiBoxTestRun.kt @@ -0,0 +1,14 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@kotlin.wasm.WasmExport +fun runBoxTest(): Boolean { + val boxResult = box() //TODO: Support non-root package box functions + val isOk = boxResult == "OK" + if (!isOk) { + println("Wrong box result '${boxResult}'; Expected 'OK'") + } + return isOk +} \ No newline at end of file