From bb087b7630f034719a97c7f4602d104da632f3f5 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Sun, 6 Nov 2022 21:41:35 +0100 Subject: [PATCH] Wasm: support running WASI tests in JUnit --- .../main/java/org/teavm/junit/RunKind.java | 3 +- .../java/org/teavm/junit/TeaVMTestRunner.java | 69 +++++++-- .../java/org/teavm/junit/WasiRunStrategy.java | 131 ++++++++++++++++++ 3 files changed, 194 insertions(+), 9 deletions(-) create mode 100644 tools/junit/src/main/java/org/teavm/junit/WasiRunStrategy.java 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 ccda5b6ef..d5a26fa04 100644 --- a/tools/junit/src/main/java/org/teavm/junit/RunKind.java +++ b/tools/junit/src/main/java/org/teavm/junit/RunKind.java @@ -18,5 +18,6 @@ package org.teavm.junit; enum RunKind { JAVASCRIPT, C, - WASM + WASM, + WASI } 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 8c3802c0f..4a6ca2c86 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -60,6 +60,7 @@ import org.junit.runners.model.InitializationError; import org.teavm.backend.c.CTarget; import org.teavm.backend.c.generate.CNameProvider; import org.teavm.backend.javascript.JavaScriptTarget; +import org.teavm.backend.wasm.WasmRuntimeType; import org.teavm.backend.wasm.WasmTarget; import org.teavm.callgraph.CallGraph; import org.teavm.debugging.information.DebugInformation; @@ -110,6 +111,8 @@ public class TeaVMTestRunner extends Runner implements Filterable { static final String JS_DECODE_STACK = "teavm.junit.js.decodeStack"; private static final String C_ENABLED = "teavm.junit.c"; private static final String WASM_ENABLED = "teavm.junit.wasm"; + private static final String WASI_ENABLED = "teavm.junit.wasi"; + private static final String WASI_RUNNER = "teavm.junit.wasi.runner"; private static final String C_COMPILER = "teavm.junit.c.compiler"; private static final String C_LINE_NUMBERS = "teavm.junit.c.lineNumbers"; private static final String MINIFIED = "teavm.junit.minified"; @@ -192,6 +195,10 @@ public class TeaVMTestRunner extends Runner implements Filterable { if (cCommand != null) { runners.get(RunKind.C).strategy = new CRunStrategy(cCommand); } + String wasiCommand = System.getProperty(WASI_RUNNER); + if (wasiCommand != null) { + runners.get(RunKind.WASI).strategy = new WasiRunStrategy(wasiCommand); + } runStrategyName = System.getProperty(WASM_RUNNER); if (runStrategyName != null) { @@ -405,7 +412,17 @@ public class TeaVMTestRunner extends Runner implements Filterable { } for (TeaVMTestConfiguration configuration : getWasmConfigurations()) { - CompileResult result = compileToWasm(wholeClass(children), "classTest", configuration, outputPath); + CompileResult result = compileToWasm(WasmRuntimeType.TEAVM, wholeClass(children), "classTest", + configuration, outputPath); + if (!result.success) { + hasErrors = true; + notifier.fireTestFailure(createFailure(description, result)); + } + } + + for (TeaVMTestConfiguration configuration : getWasiConfigurations()) { + CompileResult result = compileToWasm(WasmRuntimeType.WASI, wholeClass(children), "classTest", + configuration, outputPath); if (!result.success) { hasErrors = true; notifier.fireTestFailure(createFailure(description, result)); @@ -509,6 +526,15 @@ public class TeaVMTestRunner extends Runner implements Filterable { } } + for (TeaVMTestConfiguration configuration : getWasiConfigurations()) { + File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm"); + runs.add(createTestRun(configuration, testPath, child, RunKind.WASI, reference.toString(), + notifier, onSuccess)); + File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); + properties.put("SCRIPT", "../" + testPath.getName()); + properties.put("IDENTIFIER", reference.toString()); + } + for (TeaVMTestConfiguration configuration : getCConfigurations()) { File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), true, ".c"); runs.add(createTestRun(configuration, testPath, child, RunKind.C, reference.toString(), @@ -549,8 +575,8 @@ public class TeaVMTestRunner extends Runner implements Filterable { } for (TeaVMTestConfiguration configuration : getWasmConfigurations()) { - CompileResult compileResult = compileToWasm(singleTest(child), "test", configuration, - outputPath); + CompileResult compileResult = compileToWasm(WasmRuntimeType.TEAVM, singleTest(child), + "test", configuration, outputPath); TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASM, onSuccess); if (run != null) { runs.add(run); @@ -567,6 +593,19 @@ public class TeaVMTestRunner extends Runner implements Filterable { } } } + + for (TeaVMTestConfiguration configuration : getWasiConfigurations()) { + CompileResult compileResult = compileToWasm(WasmRuntimeType.WASI, singleTest(child), "test", + configuration, outputPath); + TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASI, onSuccess); + if (run != null) { + runs.add(run); + + File testPath = getOutputFile(outputPath, "test", configuration.getSuffix(), false, ".wasm"); + properties.put("SCRIPT", testPath.getName()); + properties.put("IDENTIFIER", ""); + } + } } catch (Throwable e) { notifier.fireTestFailure(new Failure(describeChild(child), e)); notifier.fireTestFinished(describeChild(child)); @@ -970,8 +1009,6 @@ public class TeaVMTestRunner extends Runner implements Filterable { info.runner.init(); } info.runner.run(run); - - RunKind kind = run.getKind(); } } @@ -1059,9 +1096,14 @@ public class TeaVMTestRunner extends Runner implements Filterable { return cTarget; } - private CompileResult compileToWasm(Consumer additionalProcessing, String baseName, - TeaVMTestConfiguration configuration, File path) { - return compile(configuration, WasmTarget::new, TestNativeEntryPoint.class.getName(), path, + private CompileResult compileToWasm(WasmRuntimeType runtimeType, Consumer additionalProcessing, + String baseName, TeaVMTestConfiguration configuration, File path) { + Supplier targetSupplier = () -> { + WasmTarget target = new WasmTarget(); + target.setRuntimeType(runtimeType); + return target; + }; + return compile(configuration, targetSupplier, TestNativeEntryPoint.class.getName(), path, ".wasm", null, false, additionalProcessing, baseName); } @@ -1234,6 +1276,17 @@ public class TeaVMTestRunner extends Runner implements Filterable { return configurations; } + private List> getWasiConfigurations() { + List> configurations = new ArrayList<>(); + if (Boolean.getBoolean(WASI_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/WasiRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/WasiRunStrategy.java new file mode 100644 index 000000000..af110381d --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/WasiRunStrategy.java @@ -0,0 +1,131 @@ +/* + * 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. + */ +package org.teavm.junit; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentLinkedQueue; + +class WasiRunStrategy implements TestRunStrategy { + private String runCommand; + + WasiRunStrategy(String runCommand) { + this.runCommand = runCommand; + } + + @Override + public void beforeAll() { + } + + @Override + public void afterAll() { + } + + @Override + public void beforeThread() { + } + + @Override + public void afterThread() { + } + + @Override + public void runTest(TestRun run) throws IOException { + try { + List commandLine = new ArrayList<>(); + commandLine.add(this.runCommand); + commandLine.add(new File(run.getBaseDirectory(), run.getFileName()).getAbsolutePath()); + if (run.getArgument() != null) { + commandLine.add(run.getArgument()); + } + List runtimeOutput = new ArrayList<>(); + List stdout = new ArrayList<>(); + synchronized (this) { + runProcess(new ProcessBuilder(commandLine.toArray(new String[0])).start(), runtimeOutput, stdout); + } + if (!stdout.isEmpty() && stdout.get(stdout.size() - 1).equals("SUCCESS")) { + writeLines(runtimeOutput); + run.getCallback().complete(); + } else { + run.getCallback().error(new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput))); + } + } catch (InterruptedException e) { + run.getCallback().complete(); + } + } + + private String mergeLines(List lines) { + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line).append('\n'); + } + return sb.toString(); + } + + private void writeLines(List lines) { + for (String line : lines) { + System.out.println(line); + } + } + + private boolean runProcess(Process process, List output, List stdout) throws InterruptedException { + BufferedReader stdin = new BufferedReader(new InputStreamReader(process.getInputStream())); + BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())); + ConcurrentLinkedQueue lines = new ConcurrentLinkedQueue<>(); + + Thread thread = new Thread(() -> { + try { + while (true) { + String line = stderr.readLine(); + if (line == null) { + break; + } + lines.add(line); + } + } catch (IOException e) { + // do nothing + } + }); + thread.setDaemon(true); + thread.start(); + + try { + while (true) { + String line = stdin.readLine(); + if (line == null) { + break; + } + lines.add(line); + stdout.add(line); + if (lines.size() > 10000) { + output.addAll(lines); + process.destroy(); + return false; + } + } + } catch (IOException e) { + // do nothing + } + + boolean result = process.waitFor() == 0; + output.addAll(lines); + return result; + } +}