Wasm: support running WASI tests in JUnit

This commit is contained in:
Alexey Andreev 2022-11-06 21:41:35 +01:00
parent 8f4e4e811f
commit bb087b7630
3 changed files with 194 additions and 9 deletions

View File

@ -18,5 +18,6 @@ package org.teavm.junit;
enum RunKind { enum RunKind {
JAVASCRIPT, JAVASCRIPT,
C, C,
WASM WASM,
WASI
} }

View File

@ -60,6 +60,7 @@ import org.junit.runners.model.InitializationError;
import org.teavm.backend.c.CTarget; import org.teavm.backend.c.CTarget;
import org.teavm.backend.c.generate.CNameProvider; import org.teavm.backend.c.generate.CNameProvider;
import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.backend.wasm.WasmRuntimeType;
import org.teavm.backend.wasm.WasmTarget; import org.teavm.backend.wasm.WasmTarget;
import org.teavm.callgraph.CallGraph; import org.teavm.callgraph.CallGraph;
import org.teavm.debugging.information.DebugInformation; 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"; static final String JS_DECODE_STACK = "teavm.junit.js.decodeStack";
private static final String C_ENABLED = "teavm.junit.c"; private static final String C_ENABLED = "teavm.junit.c";
private static final String WASM_ENABLED = "teavm.junit.wasm"; 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_COMPILER = "teavm.junit.c.compiler";
private static final String C_LINE_NUMBERS = "teavm.junit.c.lineNumbers"; private static final String C_LINE_NUMBERS = "teavm.junit.c.lineNumbers";
private static final String MINIFIED = "teavm.junit.minified"; private static final String MINIFIED = "teavm.junit.minified";
@ -192,6 +195,10 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (cCommand != null) { if (cCommand != null) {
runners.get(RunKind.C).strategy = new CRunStrategy(cCommand); 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); runStrategyName = System.getProperty(WASM_RUNNER);
if (runStrategyName != null) { if (runStrategyName != null) {
@ -405,7 +412,17 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) { for (TeaVMTestConfiguration<WasmTarget> 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<WasmTarget> configuration : getWasiConfigurations()) {
CompileResult result = compileToWasm(WasmRuntimeType.WASI, wholeClass(children), "classTest",
configuration, outputPath);
if (!result.success) { if (!result.success) {
hasErrors = true; hasErrors = true;
notifier.fireTestFailure(createFailure(description, result)); notifier.fireTestFailure(createFailure(description, result));
@ -509,6 +526,15 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
for (TeaVMTestConfiguration<WasmTarget> 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<CTarget> configuration : getCConfigurations()) { for (TeaVMTestConfiguration<CTarget> configuration : getCConfigurations()) {
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), true, ".c"); File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), true, ".c");
runs.add(createTestRun(configuration, testPath, child, RunKind.C, reference.toString(), runs.add(createTestRun(configuration, testPath, child, RunKind.C, reference.toString(),
@ -549,8 +575,8 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) { for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) {
CompileResult compileResult = compileToWasm(singleTest(child), "test", configuration, CompileResult compileResult = compileToWasm(WasmRuntimeType.TEAVM, singleTest(child),
outputPath); "test", configuration, outputPath);
TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASM, onSuccess); TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASM, onSuccess);
if (run != null) { if (run != null) {
runs.add(run); runs.add(run);
@ -567,6 +593,19 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
} }
for (TeaVMTestConfiguration<WasmTarget> 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) { } catch (Throwable e) {
notifier.fireTestFailure(new Failure(describeChild(child), e)); notifier.fireTestFailure(new Failure(describeChild(child), e));
notifier.fireTestFinished(describeChild(child)); notifier.fireTestFinished(describeChild(child));
@ -970,8 +1009,6 @@ public class TeaVMTestRunner extends Runner implements Filterable {
info.runner.init(); info.runner.init();
} }
info.runner.run(run); info.runner.run(run);
RunKind kind = run.getKind();
} }
} }
@ -1059,9 +1096,14 @@ public class TeaVMTestRunner extends Runner implements Filterable {
return cTarget; return cTarget;
} }
private CompileResult compileToWasm(Consumer<TeaVM> additionalProcessing, String baseName, private CompileResult compileToWasm(WasmRuntimeType runtimeType, Consumer<TeaVM> additionalProcessing,
TeaVMTestConfiguration<WasmTarget> configuration, File path) { String baseName, TeaVMTestConfiguration<WasmTarget> configuration, File path) {
return compile(configuration, WasmTarget::new, TestNativeEntryPoint.class.getName(), path, Supplier<WasmTarget> targetSupplier = () -> {
WasmTarget target = new WasmTarget();
target.setRuntimeType(runtimeType);
return target;
};
return compile(configuration, targetSupplier, TestNativeEntryPoint.class.getName(), path,
".wasm", null, false, additionalProcessing, baseName); ".wasm", null, false, additionalProcessing, baseName);
} }
@ -1234,6 +1276,17 @@ public class TeaVMTestRunner extends Runner implements Filterable {
return configurations; return configurations;
} }
private List<TeaVMTestConfiguration<WasmTarget>> getWasiConfigurations() {
List<TeaVMTestConfiguration<WasmTarget>> 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<TeaVMTestConfiguration<CTarget>> getCConfigurations() { private List<TeaVMTestConfiguration<CTarget>> getCConfigurations() {
List<TeaVMTestConfiguration<CTarget>> configurations = new ArrayList<>(); List<TeaVMTestConfiguration<CTarget>> configurations = new ArrayList<>();
if (Boolean.getBoolean(C_ENABLED)) { if (Boolean.getBoolean(C_ENABLED)) {

View File

@ -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<String> 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<String> runtimeOutput = new ArrayList<>();
List<String> 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<String> lines) {
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(line).append('\n');
}
return sb.toString();
}
private void writeLines(List<String> lines) {
for (String line : lines) {
System.out.println(line);
}
}
private boolean runProcess(Process process, List<String> output, List<String> stdout) throws InterruptedException {
BufferedReader stdin = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream()));
ConcurrentLinkedQueue<String> 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;
}
}