From b087610c2c651754ddddc3b6cc09ff60364bc163 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 8 May 2018 19:59:15 +0300 Subject: [PATCH] Wasm backend: make JUnit tests work --- .../teavm/classlib/java/lang/TCharacter.java | 4 +- .../java/lang/TConsoleOutputStreamStderr.java | 2 +- .../org/teavm/classlib/java/lang/TDouble.java | 6 +- .../org/teavm/classlib/java/lang/TFloat.java | 6 +- .../org/teavm/classlib/java/lang/TMath.java | 28 +++--- .../org/teavm/classlib/java/lang/TSystem.java | 2 +- .../org/teavm/classlib/java/util/TRandom.java | 2 +- .../wasm/generate/WasmClassGenerator.java | 27 ++++-- .../wasm/intrinsics/PlatformIntrinsic.java | 2 + .../org/teavm/backend/wasm/wasm-runtime.js | 89 +++++++++++++++++++ tests/src/test/js/frame.js | 69 ++++++++++++-- tests/src/test/js/src/run-tests.js | 65 +++++++++++--- .../main/java/org/teavm/junit/RunKind.java | 3 +- .../teavm/junit/TeaVMTestConfiguration.java | 38 +++++++- .../java/org/teavm/junit/TeaVMTestRunner.java | 33 ++++++- .../java/org/teavm/junit/TestEntryPoint.java | 1 + .../main/resources/teavm-run-test-wasm.html | 29 ++++++ 17 files changed, 349 insertions(+), 57 deletions(-) create mode 100644 core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js create mode 100644 tools/junit/src/main/resources/teavm-run-test-wasm.html diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TCharacter.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TCharacter.java index 488e1c541..2f091766a 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TCharacter.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TCharacter.java @@ -242,7 +242,7 @@ public class TCharacter extends TObject implements TComparable { return toLowerCaseSystem(codePoint); } - @Import(module = "runtime", name = "towlower") + @Import(module = "teavm", name = "towlower") private static native int toLowerCaseSystem(int codePoint); public static char toUpperCase(char ch) { @@ -258,7 +258,7 @@ public class TCharacter extends TObject implements TComparable { return toUpperCaseSystem(codePoint); } - @Import(module = "runtime", name = "towupper") + @Import(module = "teavm", name = "towupper") private static native int toUpperCaseSystem(int codePoint); public static int digit(char ch, int radix) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TConsoleOutputStreamStderr.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TConsoleOutputStreamStderr.java index 530d7d2f5..3e63f6fda 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TConsoleOutputStreamStderr.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TConsoleOutputStreamStderr.java @@ -32,6 +32,6 @@ class TConsoleOutputStreamStderr extends TOutputStream { writeImpl(b); } - @Import(name = "putwchar", module = "runtime") + @Import(name = "putwchar", module = "teavm") static native void writeImpl(int b); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java index 90e32736d..acdc85d15 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java @@ -222,15 +222,15 @@ public class TDouble extends TNumber implements TComparable { } @JSBody(params = "v", script = "return isNaN(v);") - @Import(module = "runtime", name = "isnan") + @Import(module = "teavm", name = "isnan") public static native boolean isNaN(double v); @JSBody(script = "return NaN;") - @Import(module = "runtime", name = "TeaVM_getNaN") + @Import(module = "teavm", name = "TeaVM_getNaN") private static native double getNaN(); @JSBody(params = "v", script = "return !isFinite(v);") - @Import(module = "runtime", name = "isinf") + @Import(module = "teavm", name = "isinf") public static native boolean isInfinite(double v); public static long doubleToRawLongBits(double value) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java index feb1b993b..1a72e0315 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java @@ -90,7 +90,7 @@ public class TFloat extends TNumber implements TComparable { } @JSBody(params = "v", script = "return isNaN(v);") - @Import(module = "runtime", name = "isnan") + @Import(module = "teavm", name = "isnan") public static native boolean isNaN(float v); public static boolean isInfinite(float v) { @@ -98,11 +98,11 @@ public class TFloat extends TNumber implements TComparable { } @JSBody(params = "v", script = "return isFinite(v);") - @Import(module = "runtime", name = "isfinite") + @Import(module = "teavm", name = "isfinite") private static native boolean isFinite(float v); @JSBody(script = "return NaN;") - @Import(module = "runtime", name = "TeaVM_getNaN") + @Import(module = "teavm", name = "TeaVM_getNaN") private static native float getNaN(); public static float parseFloat(TString string) throws TNumberFormatException { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java index 42b04aa20..9ff246aed 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java @@ -26,27 +26,27 @@ public final class TMath extends TObject { } @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "sin") + @Import(module = "teavmMath", name = "sin") public static native double sin(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "cos") + @Import(module = "teavmMath", name = "cos") public static native double cos(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "tan") + @Import(module = "teavmMath", name = "tan") public static native double tan(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "asin") + @Import(module = "teavmMath", name = "asin") public static native double asin(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "acos") + @Import(module = "teavmMath", name = "acos") public static native double acos(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "atan") + @Import(module = "teavmMath", name = "atan") public static native double atan(double a); public static double toRadians(double angdeg) { @@ -58,11 +58,11 @@ public final class TMath extends TObject { } @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "exp") + @Import(module = "teavmMath", name = "exp") public static native double exp(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "log") + @Import(module = "teavmMath", name = "log") public static native double log(double a); public static double log10(double a) { @@ -70,7 +70,7 @@ public final class TMath extends TObject { } @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "sqrt") + @Import(module = "teavmMath", name = "sqrt") public static native double sqrt(double a); public static double cbrt(double a) { @@ -83,15 +83,15 @@ public final class TMath extends TObject { } @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "ceil") + @Import(module = "teavmMath", name = "ceil") public static native double ceil(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "floor") + @Import(module = "teavmMath", name = "floor") public static native double floor(double a); @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "pow") + @Import(module = "teavmMath", name = "pow") public static native double pow(double x, double y); public static double rint(double a) { @@ -99,7 +99,7 @@ public final class TMath extends TObject { } @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "atan2") + @Import(module = "teavmMath", name = "atan2") public static native double atan2(double y, double x); public static int round(float a) { @@ -111,7 +111,7 @@ public final class TMath extends TObject { } @GeneratedBy(MathNativeGenerator.class) - @Import(module = "math", name = "random") + @Import(module = "teavmMath", name = "random") public static native double random(); public static int min(int a, int b) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java index 74cbb1571..de67e7102 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java @@ -114,7 +114,7 @@ public final class TSystem extends TObject { } } - @Import(name = "currentTimeMillis", module = "runtime") + @Import(name = "currentTimeMillis", module = "teavm") private static native double currentTimeMillisWasm(); @Import(name = "currentTimeMillis") diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java b/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java index 46530bafa..dd31f9f90 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java @@ -114,6 +114,6 @@ public class TRandom extends TObject implements TSerializable { } @JSBody(script = "return Math.random();") - @Import(module = "math", name = "random") + @Import(module = "teavmMath", name = "random") private static native double random(); } diff --git a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java index 95e93b017..97b3d0387 100644 --- a/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java +++ b/core/src/main/java/org/teavm/backend/wasm/generate/WasmClassGenerator.java @@ -73,9 +73,11 @@ public class WasmClassGenerator { DataPrimitives.ADDRESS, /* item type */ DataPrimitives.ADDRESS, /* array type */ DataPrimitives.INT, /* isInstance function */ + DataPrimitives.INT, /* init function */ DataPrimitives.ADDRESS, /* parent */ DataPrimitives.ADDRESS, /* enum values */ - DataPrimitives.ADDRESS /* layout */); + DataPrimitives.ADDRESS, /* layout */ + DataPrimitives.ADDRESS /* simple name */); private IntegerArray staticGcRoots = new IntegerArray(1); private int staticGcRootsAddress; @@ -87,9 +89,11 @@ public class WasmClassGenerator { private static final int CLASS_ITEM_TYPE = 6; private static final int CLASS_ARRAY_TYPE = 7; private static final int CLASS_IS_INSTANCE = 8; - private static final int CLASS_PARENT = 9; - private static final int CLASS_ENUM_VALUES = 10; - private static final int CLASS_LAYOUT = 11; + private static final int CLASS_INIT = 9; + private static final int CLASS_PARENT = 10; + private static final int CLASS_ENUM_VALUES = 11; + private static final int CLASS_LAYOUT = 12; + private static final int CLASS_SIMPLE_NAME = 13; public WasmClassGenerator(ClassReaderSource classSource, VirtualTableProvider vtableProvider, TagRegistry tagRegistry, BinaryWriter binaryWriter) { @@ -170,6 +174,8 @@ public class WasmClassGenerator { binaryData.data.setInt(CLASS_IS_INSTANCE, functionTable.size()); binaryData.data.setInt(CLASS_CANARY, RuntimeClass.computeCanary(4, 0)); functionTable.add(Mangling.mangleIsSupertype(type)); + binaryData.data.setAddress(CLASS_SIMPLE_NAME, 0); + binaryData.data.setInt(CLASS_INIT, -1); binaryData.start = binaryWriter.append(vtableSize > 0 ? wrapper : binaryData.data); itemBinaryData.data.setAddress(CLASS_ARRAY_TYPE, binaryData.start); @@ -181,6 +187,8 @@ public class WasmClassGenerator { value.setInt(CLASS_SIZE, size); value.setInt(CLASS_FLAGS, RuntimeClass.PRIMITIVE); value.setInt(CLASS_IS_INSTANCE, functionTable.size()); + value.setAddress(CLASS_SIMPLE_NAME, 0); + value.setInt(CLASS_INIT, -1); functionTable.add(Mangling.mangleIsSupertype(type)); return value; } @@ -248,7 +256,16 @@ public class WasmClassGenerator { flags |= RuntimeClass.ENUM; } + if (cls != null && binaryData.start >= 0 + && cls.getMethod(new MethodDescriptor("", ValueType.VOID)) != null) { + header.setInt(CLASS_INIT, functionTable.size()); + functionTable.add(Mangling.mangleInitializer(name)); + } else { + header.setInt(CLASS_INIT, -1); + } + header.setInt(CLASS_FLAGS, flags); + header.setAddress(CLASS_SIMPLE_NAME, 0); return vtable != null ? wrapper : header; } @@ -500,7 +517,7 @@ public class WasmClassGenerator { } public boolean hasClinit(String className) { - if (isStructure(className)) { + if (isStructure(className) || className.equals(Address.class.getName())) { return false; } ClassReader cls = classSource.get(className); diff --git a/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformIntrinsic.java b/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformIntrinsic.java index 8fa17e2f0..4e11aa14d 100644 --- a/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformIntrinsic.java +++ b/core/src/main/java/org/teavm/backend/wasm/intrinsics/PlatformIntrinsic.java @@ -35,6 +35,7 @@ public class PlatformIntrinsic implements WasmIntrinsic { case "getPlatformObject": case "asJavaClass": case "getName": + case "createQueue": return true; default: return false; @@ -48,6 +49,7 @@ public class PlatformIntrinsic implements WasmIntrinsic { case "asJavaClass": return manager.generate(invocation.getArguments().get(0)); case "getName": + case "createQueue": return new WasmInt32Constant(0); default: throw new IllegalArgumentException(invocation.getMethod().toString()); diff --git a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js new file mode 100644 index 000000000..f4b17fbef --- /dev/null +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js @@ -0,0 +1,89 @@ +/* + * Copyright 2018 Alexey Andreev. + * + * 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. + */ + +var TeaVM = TeaVM || {}; +TeaVM.wasm = function() { + let lineBuffer = ""; + function putwchar(charCode) { + if (charCode === 10) { + console.log(lineBuffer); + lineBuffer = ""; + } else { + lineBuffer += String.fromCharCode(charCode); + } + } + function towlower(code) { + return String.fromCharCode(code).toLowerCase().charCodeAt(0); + } + function towupper(code) { + return String.fromCharCode(code).toUpperCase().charCodeAt(0); + } + function currentTimeMillis() { + return new Date().getTime(); + } + + function importDefaults(obj) { + obj.teavm = { + currentTimeMillis: currentTimeMillis, + isnan: isNaN, + TeaVM_getNaN: function() { return NaN; }, + isinf: function(n) { return !isFinite(n) }, + isfinite: isFinite, + putwchar: putwchar, + towlower: towlower, + towupper: towupper + }; + + obj.teavmMath = Math; + } + + function run(path, options) { + if (!options) { + options = {}; + } + + let callback = typeof options.callback !== "undefined" ? options.callback : function() {}; + let errorCallback = typeof options.errorCallback !== "undefined" ? options.errorCallback : function() {}; + + let importObj = {}; + importDefaults(importObj); + if (typeof options.installImports !== "undefined") { + options.installImports(importObj); + } + + let xhr = new XMLHttpRequest(); + xhr.responseType = "arraybuffer"; + xhr.open("GET", path); + + xhr.onload = function() { + let response = xhr.response; + if (!response) { + return; + } + + WebAssembly.instantiate(response, importObj).then(function(resultObject) { + resultObject.instance.exports.main(); + callback(resultObject); + }).catch(function(error) { + console.log("Error loading WebAssembly %o", error); + errorCallback(error); + }); + }; + xhr.send(); + } + + return { importDefaults: importDefaults, run: run }; +}(); diff --git a/tests/src/test/js/frame.js b/tests/src/test/js/frame.js index a5e20f032..d340f03bc 100644 --- a/tests/src/test/js/frame.js +++ b/tests/src/test/js/frame.js @@ -18,26 +18,39 @@ window.addEventListener("message", event => { let request = event.data; - appendFiles(request.files, 0, () => { - launchTest(response => { - event.source.postMessage(response, "*"); - }); - }, error => { - event.source.postMessage({ status: "failed", errorMessage: error }, "*"); - }); + switch (request.type) { + case "js": + appendFiles(request.files, 0, () => { + launchTest(response => { + event.source.postMessage(response, "*"); + }); + }, error => { + event.source.postMessage({ status: "failed", errorMessage: error }, "*"); + }); + break; + case "wasm": + appendFiles(request.files.filter(f => f.endsWith(".js")), 0, () => { + launchWasmTest(request.files.filter(f => f.endsWith(".wasm"))[0], response => { + event.source.postMessage(response, "*"); + }); + }, error => { + event.source.postMessage({ status: "failed", errorMessage: error }, "*"); + }); + break; + } }); function appendFiles(files, index, callback, errorCallback) { if (index === files.length) { callback(); } else { - let fileName = "file://" + files[index]; + let fileName = files[index]; let script = document.createElement("script"); script.onload = () => { appendFiles(files, index + 1, callback, errorCallback); }; script.onerror = () => { - errorCallback("failed to load script" + fileName); + errorCallback("failed to load script " + fileName); }; script.src = fileName; document.body.appendChild(script); @@ -81,6 +94,44 @@ function launchTest(callback) { } } +function launchWasmTest(path, callback) { + var output = []; + var outputBuffer = ""; + + function putwchar(charCode) { + if (charCode === 10) { + switch (outputBuffer) { + case "SUCCESS": + callback({status: "OK"}); + break; + case "FAILED": + callback({ + status: "failed", + errorMessage: output.join("\n") + }); + break; + default: + output.push(TeaVM_outputBuffer); + outputBuffer = ""; + } + } else { + outputBuffer += String.fromCharCode(charCode); + } + } + + TeaVM.wasm.run(path, { + installImports: function(o) { + o.teavm.putwchar = putwchar; + }, + errorCallback: function(err) { + callback({ + status: "failed", + errorMessage: err.message + '\n' + err.stack + }); + } + }); +} + function start() { window.parent.postMessage("ready", "*"); } \ No newline at end of file diff --git a/tests/src/test/js/src/run-tests.js b/tests/src/test/js/src/run-tests.js index 8d1e277af..bdbc2f26a 100644 --- a/tests/src/test/js/src/run-tests.js +++ b/tests/src/test/js/src/run-tests.js @@ -16,17 +16,20 @@ "use strict"; import * as fs from "./promise-fs.js"; -import * as nodePath from "path"; import * as http from "http"; import {server as WebSocketServer} from "websocket"; const TEST_FILE_NAME = "test.js"; const RUNTIME_FILE_NAME = "runtime.js"; +const WASM_RUNTIME_FILE_NAME = "test.wasm-runtime.js"; const TEST_FILES = [ - { file: TEST_FILE_NAME, name: "simple" }, - { file: "test-min.js", name: "minified" }, - { file: "test-optimized.js", name: "optimized" } + { file: TEST_FILE_NAME, name: "simple", type: "js" }, + { file: "test-min.js", name: "minified", type: "js" }, + { file: "test-optimized.js", name: "optimized", type: "js" }, + { file: "test.wasm", name: "wasm", type: "wasm" }, + { file: "test-optimized.wasm", name: "wasm-optimized", type: "wasm" } ]; +const SERVER_PREFIX = "http://localhost:9090/"; let totalTests = 0; class TestSuite { @@ -37,20 +40,30 @@ class TestSuite { } } class TestCase { - constructor(name, files) { + constructor(type, name, files) { + this.type = type; this.name = name; this.files = files; } } +let rootDir = process.argv[2]; +if (rootDir.endsWith("/")) { + rootDir = rootDir.substring(0, rootDir.length - 1); +} + async function runAll() { const rootSuite = new TestSuite("root"); console.log("Searching tests"); - await walkDir(process.argv[2], "root", rootSuite); + await walkDir("", "root", rootSuite); console.log("Running tests"); const server = http.createServer((request, response) => { + if (request.url.endsWith(".js") || request.url.endsWith(".wasm")) { + serveFile(rootDir + "/" + request.url, response); + return; + } response.writeHead(404); response.end(); }); @@ -94,14 +107,37 @@ async function runAll() { } } +async function serveFile(path, response) { + const stat = await fs.stat(path); + const contentType = path.endsWith(".wasm") ? "application/octet-stream" : "text/javascript"; + if (stat.isFile()) { + const content = await fs.readFile(path); + response.writeHead(200, { 'Content-Type': contentType, 'Access-Control-Allow-Origin': "*" }); + response.end(content, 'utf-8'); + } else { + response.writeHead(404); + response.end(); + } +} + async function walkDir(path, name, suite) { - const files = await fs.readdir(path); - if (files.includes(TEST_FILE_NAME) && files.includes(RUNTIME_FILE_NAME)) { - for (const { file: fileName, name: profileName } of TEST_FILES) { + const files = await fs.readdir(rootDir + "/" + path); + if (files.includes(WASM_RUNTIME_FILE_NAME) || files.includes(RUNTIME_FILE_NAME)) { + for (const { file: fileName, name: profileName, type: type } of TEST_FILES) { if (files.includes(fileName)) { - suite.testCases.push(new TestCase( - name + " " + profileName, - [path + "/" + RUNTIME_FILE_NAME, path + "/" + fileName])); + switch (type) { + case "js": + suite.testCases.push(new TestCase( + "js", name + " " + profileName, + [SERVER_PREFIX + path + "/" + RUNTIME_FILE_NAME, SERVER_PREFIX + path + "/" + fileName])); + break; + case "wasm": + suite.testCases.push(new TestCase( + "wasm", name + " " + profileName, + [SERVER_PREFIX + path + "/" + WASM_RUNTIME_FILE_NAME, + SERVER_PREFIX + path + "/" + fileName])); + break; + } totalTests++; } } @@ -110,7 +146,7 @@ async function walkDir(path, name, suite) { suite.testSuites.push(childSuite); await Promise.all(files.map(async file => { const filePath = path + "/" + file; - const stat = await fs.stat(filePath); + const stat = await fs.stat(rootDir + "/" + filePath); if (stat.isDirectory()) { await walkDir(filePath, file, childSuite); } @@ -148,8 +184,9 @@ class TestRunner { let request = { id: this.requestIdGen++ }; request.tests = suite.testCases.map(testCase => { return { + type: testCase.type, name: testCase.name, - files: testCase.files.map(fileName => nodePath.resolve(process.cwd(), fileName)) + files: testCase.files }; }); this.testsRun += suite.testCases.length; diff --git a/tools/junit/src/main/java/org/teavm/junit/RunKind.java b/tools/junit/src/main/java/org/teavm/junit/RunKind.java index ea7ebd316..ccda5b6ef 100644 --- a/tools/junit/src/main/java/org/teavm/junit/RunKind.java +++ b/tools/junit/src/main/java/org/teavm/junit/RunKind.java @@ -17,5 +17,6 @@ package org.teavm.junit; enum RunKind { JAVASCRIPT, - C + C, + WASM } diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java index 14527d005..0be5bf2c3 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestConfiguration.java @@ -17,6 +17,7 @@ package org.teavm.junit; import org.teavm.backend.c.CTarget; import org.teavm.backend.javascript.JavaScriptTarget; +import org.teavm.backend.wasm.WasmTarget; import org.teavm.vm.TeaVM; import org.teavm.vm.TeaVMOptimizationLevel; import org.teavm.vm.TeaVMTarget; @@ -79,6 +80,40 @@ interface TeaVMTestConfiguration { } }; + TeaVMTestConfiguration WASM_DEFAULT = new TeaVMTestConfiguration() { + @Override + public String getSuffix() { + return ""; + } + + @Override + public void apply(TeaVM vm) { + vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE); + } + + @Override + public void apply(WasmTarget target) { + target.setMinHeapSize(32 * 1024 * 1024); + target.setWastEmitted(true); + } + }; + + TeaVMTestConfiguration WASM_OPTIMIZED = new TeaVMTestConfiguration() { + @Override + public String getSuffix() { + return "optimized"; + } + + @Override + public void apply(TeaVM vm) { + vm.setOptimizationLevel(TeaVMOptimizationLevel.FULL); + } + + @Override + public void apply(WasmTarget target) { + } + }; + TeaVMTestConfiguration C_DEFAULT = new TeaVMTestConfiguration() { @Override public String getSuffix() { @@ -95,11 +130,10 @@ interface TeaVMTestConfiguration { } }; - TeaVMTestConfiguration C_OPTIMIZED = new TeaVMTestConfiguration() { @Override public String getSuffix() { - return ""; + return "optimized"; } @Override diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java index 97434cfcf..91397a094 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -52,6 +52,7 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.model.InitializationError; import org.teavm.backend.c.CTarget; import org.teavm.backend.javascript.JavaScriptTarget; +import org.teavm.backend.wasm.WasmTarget; import org.teavm.callgraph.CallGraph; import org.teavm.diagnostics.DefaultProblemTextConsumer; import org.teavm.diagnostics.Problem; @@ -78,7 +79,8 @@ public class TeaVMTestRunner extends Runner implements Filterable { private static final String SELENIUM_URL = "teavm.junit.js.selenium.url"; private static final String JS_ENABLED = "teavm.junit.js"; private static final String C_ENABLED = "teavm.junit.c"; - private static final String C_COMPILER = "teavm.junit.c-compiler"; + private static final String WASM_ENABLED = "teavm.junit.wasm"; + private static final String C_COMPILER = "teavm.junit.c.compiler"; private static final String MINIFIED = "teavm.junit.minified"; private static final String OPTIMIZED = "teavm.junit.optimized"; @@ -275,6 +277,15 @@ public class TeaVMTestRunner extends Runner implements Filterable { runs.add(run); } } + + for (TeaVMTestConfiguration configuration : getWasmConfigurations()) { + TestRun run = compile(child, notifier, RunKind.WASM, + m -> compileToWasm(m, configuration, outputPath), onSuccess.get(0)); + if (run != null) { + runs.add(run); + } + } + } catch (Throwable e) { notifier.fireTestFailure(new Failure(description, e)); notifier.fireTestFinished(description); @@ -414,7 +425,9 @@ public class TeaVMTestRunner extends Runner implements Filterable { private void copyJsFilesTo(File path) throws IOException { resourceToFile("org/teavm/backend/javascript/runtime.js", new File(path, "runtime.js")); + resourceToFile("org/teavm/backend/wasm/wasm-runtime.js", new File(path, "test.wasm-runtime.js")); resourceToFile("teavm-run-test.html", new File(path, "run-test.html")); + resourceToFile("teavm-run-test-wasm.html", new File(path, "run-test-wasm.html")); } private CompileResult compileToJs(Method method, TeaVMTestConfiguration configuration, @@ -434,6 +447,13 @@ public class TeaVMTestRunner extends Runner implements Filterable { }, path, ".c"); } + private CompileResult compileToWasm(Method method, TeaVMTestConfiguration configuration, + File path) { + return compileTest(method, configuration, WasmTarget::new, vm -> { + vm.entryPoint("main", new MethodReference(TestEntryPoint.class, "main", String[].class, void.class)); + }, path, ".wasm"); + } + private CompileResult compileTest(Method method, TeaVMTestConfiguration configuration, Supplier targetSupplier, Consumer preBuild, File path, String extension) { CompileResult result = new CompileResult(); @@ -497,6 +517,17 @@ public class TeaVMTestRunner extends Runner implements Filterable { return configurations; } + private List> getWasmConfigurations() { + List> configurations = new ArrayList<>(); + if (Boolean.getBoolean(WASM_ENABLED)) { + configurations.add(TeaVMTestConfiguration.WASM_DEFAULT); + if (Boolean.getBoolean(OPTIMIZED)) { + configurations.add(TeaVMTestConfiguration.WASM_OPTIMIZED); + } + } + return configurations; + } + private List> getCConfigurations() { List> configurations = new ArrayList<>(); if (Boolean.getBoolean(C_ENABLED)) { diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java index 7eec252bc..a9ebbbfcf 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java @@ -39,6 +39,7 @@ final class TestEntryPoint { System.out.println("SUCCESS"); } catch (Throwable e) { e.printStackTrace(System.out); + System.out.println("FAILURE"); } } } diff --git a/tools/junit/src/main/resources/teavm-run-test-wasm.html b/tools/junit/src/main/resources/teavm-run-test-wasm.html new file mode 100644 index 000000000..6b95accbc --- /dev/null +++ b/tools/junit/src/main/resources/teavm-run-test-wasm.html @@ -0,0 +1,29 @@ + + + + + + TeaVM JUnit test + + + + + + + \ No newline at end of file