Simplify test runner, remove parallel run, remove HtmlUnit runner, fix Wasm tests

This commit is contained in:
Alexey Andreev 2023-09-29 21:11:23 +02:00
parent a3eb5f635f
commit 47973face1
15 changed files with 160 additions and 577 deletions

View File

@ -23,10 +23,10 @@ public class WasmSupport {
private WasmSupport() { private WasmSupport() {
} }
@Import(name = "putwcharsOut", module = "teavm") @Import(name = "putwcharsErr", module = "teavm")
public static native void putCharsStderr(Address address, int count); public static native void putCharsStderr(Address address, int count);
@Import(name = "putwcharsErr", module = "teavm") @Import(name = "putwcharsOut", module = "teavm")
public static native void putCharsStdout(Address address, int count); public static native void putCharsStdout(Address address, int count);
public static long currentTimeMillis() { public static long currentTimeMillis() {

View File

@ -213,7 +213,12 @@ TeaVM.wasm = function() {
controller.resolve = resolve; controller.resolve = resolve;
controller.reject = reject; controller.reject = reject;
wrapExport(teavm.instance.exports.start, teavm.instance)(javaArgs); try {
wrapExport(teavm.instance.exports.start, teavm.instance)(javaArgs);
} catch (e) {
reject(e);
return;
}
process(controller); process(controller);
}); });
} }

View File

@ -40,7 +40,6 @@ dependencies {
tasks.test { tasks.test {
systemProperty("teavm.junit.target", layout.buildDirectory.dir("teavm-tests").get().asFile.absolutePath) systemProperty("teavm.junit.target", layout.buildDirectory.dir("teavm-tests").get().asFile.absolutePath)
systemProperty("teavm.junit.threads", "1")
val browser = providers.gradleProperty("teavm.tests.browser").orElse("browser-chrome").get() val browser = providers.gradleProperty("teavm.tests.browser").orElse("browser-chrome").get()
systemProperty("teavm.junit.js", providers.gradleProperty("teavm.tests.js").orElse("true").get()) systemProperty("teavm.junit.js", providers.gradleProperty("teavm.tests.js").orElse("true").get())

View File

@ -33,8 +33,6 @@ dependencies {
implementation(project(":core")) implementation(project(":core"))
implementation(project(":tools:core")) implementation(project(":tools:core"))
implementation(libs.rhino)
implementation(libs.htmlunit)
implementation(libs.jackson.annotations) implementation(libs.jackson.annotations)
implementation(libs.jackson.databind) implementation(libs.jackson.databind)
implementation(libs.javax.servlet) implementation(libs.javax.servlet)

View File

@ -114,34 +114,24 @@ class BrowserRunStrategy implements TestRunStrategy {
} }
} }
@Override
public void beforeThread() {
}
@Override
public void afterThread() {
}
static class CallbackWrapper implements TestRunCallback { static class CallbackWrapper implements TestRunCallback {
private final CountDownLatch latch; private final CountDownLatch latch;
private final TestRun run; volatile Throwable error;
volatile boolean shouldRepeat; volatile boolean shouldRepeat;
CallbackWrapper(CountDownLatch latch, TestRun run) { CallbackWrapper(CountDownLatch latch) {
this.latch = latch; this.latch = latch;
this.run = run;
} }
@Override @Override
public void complete() { public void complete() {
latch.countDown(); latch.countDown();
run.getCallback().complete();
} }
@Override @Override
public void error(Throwable e) { public void error(Throwable e) {
error = e;
latch.countDown(); latch.countDown();
run.getCallback().error(e);
} }
void repeat() { void repeat() {
@ -164,14 +154,14 @@ class BrowserRunStrategy implements TestRunStrategy {
ws = wsSessionQueue.poll(1, TimeUnit.SECONDS); ws = wsSessionQueue.poll(1, TimeUnit.SECONDS);
} while (ws == null || !ws.isOpen()); } while (ws == null || !ws.isOpen());
} catch (InterruptedException e) { } catch (InterruptedException e) {
run.getCallback().error(e); Thread.currentThread().interrupt();
return true; return true;
} }
int id = idGenerator.incrementAndGet(); int id = idGenerator.incrementAndGet();
CountDownLatch latch = new CountDownLatch(1); var latch = new CountDownLatch(1);
CallbackWrapper callbackWrapper = new CallbackWrapper(latch, run); CallbackWrapper callbackWrapper = new CallbackWrapper(latch);
awaitingRuns.put(id, callbackWrapper); awaitingRuns.put(id, callbackWrapper);
JsonNodeFactory nf = objectMapper.getNodeFactory(); JsonNodeFactory nf = objectMapper.getNodeFactory();
@ -215,6 +205,15 @@ class BrowserRunStrategy implements TestRunStrategy {
wsSessionQueue.offer(ws); wsSessionQueue.offer(ws);
} }
if (callbackWrapper.error != null) {
var err = callbackWrapper.error;
if (err instanceof RuntimeException) {
throw (RuntimeException) err;
} else {
throw new RuntimeException(err);
}
}
return !callbackWrapper.shouldRepeat; return !callbackWrapper.shouldRepeat;
} }

View File

@ -33,22 +33,6 @@ class CRunStrategy implements TestRunStrategy {
this.compilerCommand = compilerCommand; this.compilerCommand = compilerCommand;
} }
@Override
public void beforeAll() {
}
@Override
public void afterAll() {
}
@Override
public void beforeThread() {
}
@Override
public void afterThread() {
}
@Override @Override
public void runTest(TestRun run) throws IOException { public void runTest(TestRun run) throws IOException {
try { try {
@ -60,8 +44,7 @@ class CRunStrategy implements TestRunStrategy {
File outputFile = new File(run.getBaseDirectory(), exeName); File outputFile = new File(run.getBaseDirectory(), exeName);
boolean compilerSuccess = compile(run.getBaseDirectory()); boolean compilerSuccess = compile(run.getBaseDirectory());
if (!compilerSuccess) { if (!compilerSuccess) {
run.getCallback().error(new RuntimeException("C compiler error")); throw new RuntimeException("C compiler error");
return;
} }
List<String> runtimeOutput = new ArrayList<>(); List<String> runtimeOutput = new ArrayList<>();
@ -77,12 +60,11 @@ class CRunStrategy implements TestRunStrategy {
} }
if (!stdout.isEmpty() && stdout.get(stdout.size() - 1).equals("SUCCESS")) { if (!stdout.isEmpty() && stdout.get(stdout.size() - 1).equals("SUCCESS")) {
writeLines(runtimeOutput); writeLines(runtimeOutput);
run.getCallback().complete();
} else { } else {
run.getCallback().error(new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput))); throw new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput));
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
run.getCallback().complete(); Thread.currentThread().interrupt();
} }
} }

View File

@ -1,204 +0,0 @@
/*
* Copyright 2016 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 com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConsole;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.NativeArray;
import net.sourceforge.htmlunit.corejs.javascript.NativeJavaObject;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import org.apache.commons.io.IOUtils;
class HtmlUnitRunStrategy implements TestRunStrategy {
private ThreadLocal<WebClient> webClient = new ThreadLocal<>();
private ThreadLocal<HtmlPage> page = new ThreadLocal<>();
private int runs;
@Override
public void beforeAll() {
}
@Override
public void afterAll() {
}
@Override
public void beforeThread() {
init();
}
@Override
public void afterThread() {
cleanUp();
}
@Override
public void runTest(TestRun run) throws IOException {
if (++runs == 50) {
runs = 0;
cleanUp();
init();
}
try {
page.set(webClient.get().getPage("about:blank"));
} catch (IOException e) {
throw new RuntimeException(e);
}
HtmlPage pageRef = page.get();
pageRef.executeJavaScript(readFile(new File(run.getBaseDirectory(), run.getFileName())));
boolean decodeStack = Boolean.parseBoolean(System.getProperty(TeaVMTestRunner.JS_DECODE_STACK, "true"));
File debugFile = decodeStack ? new File(run.getBaseDirectory(), run.getFileName() + ".teavmdbg") : null;
RhinoResultParser resultParser = new RhinoResultParser(debugFile);
AsyncResult asyncResult = new AsyncResult();
Function function = (Function) page.get().executeJavaScript(readResource("teavm-htmlunit-adapter.js"))
.getJavaScriptResult();
Object[] args = new Object[] {
run.getArgument(),
decodeStack ? createStackDecoderFunction(resultParser) : null,
new NativeJavaObject(function, asyncResult, AsyncResult.class)
};
pageRef.executeJavaScriptFunction(function, function, args, page.get());
resultParser.parseResult((Scriptable) asyncResult.getResult(), run.getCallback());
}
private Function createStackDecoderFunction(RhinoResultParser resultParser) {
return new BaseFunction() {
@Override
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
String stack = args[0].toString();
return new NativeArray(resultParser.decodeStack(stack));
}
};
}
private void cleanUp() {
Page p = page.get();
if (p != null) {
p.cleanUp();
}
for (WebWindow window : webClient.get().getWebWindows()) {
window.getJobManager().removeAllJobs();
}
page.remove();
webClient.get().close();
webClient.remove();
}
private void init() {
WebClient client = new WebClient(BrowserVersion.CHROME);
client.getWebConsole().setLogger(new WebConsole.Logger() {
@Override
public boolean isTraceEnabled() {
return false;
}
@Override
public void trace(Object message) {
}
@Override
public boolean isDebugEnabled() {
return false;
}
@Override
public void debug(Object message) {
}
@Override
public boolean isInfoEnabled() {
return true;
}
@Override
public void info(Object message) {
System.out.println(message);
}
@Override
public boolean isWarnEnabled() {
return true;
}
@Override
public void warn(Object message) {
System.out.println(message);
}
@Override
public boolean isErrorEnabled() {
return true;
}
@Override
public void error(Object message) {
System.err.println(message);
}
});
webClient.set(client);
}
private String readFile(File file) throws IOException {
try (InputStream input = new FileInputStream(file)) {
return IOUtils.toString(input, StandardCharsets.UTF_8);
}
}
private String readResource(String resourceName) throws IOException {
try (InputStream input = HtmlUnitRunStrategy.class.getClassLoader().getResourceAsStream(resourceName)) {
if (input == null) {
return "";
}
return IOUtils.toString(input, StandardCharsets.UTF_8);
}
}
public static class AsyncResult {
private CountDownLatch latch = new CountDownLatch(1);
private Object result;
public void complete(Object result) {
this.result = result;
latch.countDown();
}
public Object getResult() {
try {
latch.await(5, TimeUnit.SECONDS);
return result;
} catch (InterruptedException e) {
return null;
}
}
}
}

View File

@ -40,9 +40,6 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -123,44 +120,21 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private Class<?> testClass; private Class<?> testClass;
private boolean isWholeClassCompilation; private boolean isWholeClassCompilation;
private ClassHolderSource classSource; private static ClassHolderSource classSource;
private ClassLoader classLoader; private static ClassLoader classLoader;
private Description suiteDescription; private Description suiteDescription;
private static Map<ClassLoader, ClassHolderSource> classSources = new WeakHashMap<>(); private static File outputDir;
private File outputDir;
private Map<Method, Description> descriptions = new HashMap<>(); private Map<Method, Description> descriptions = new HashMap<>();
private static Map<RunKind, RunnerKindInfo> runners = new HashMap<>(); private static Map<RunKind, TestRunStrategy> runners = new HashMap<>();
private CountDownLatch latch;
private List<Method> filteredChildren; private List<Method> filteredChildren;
private ReferenceCache referenceCache = new ReferenceCache(); private static ReferenceCache referenceCache = new ReferenceCache();
private boolean classCompilationOk; private boolean classCompilationOk;
private List<TestRun> runsInCurrentClass = new ArrayList<>(); private List<TestRun> runsInCurrentClass = new ArrayList<>();
static class RunnerKindInfo {
volatile TestRunner runner;
volatile TestRunStrategy strategy;
}
static { static {
for (RunKind kind : RunKind.values()) {
runners.put(kind, new RunnerKindInfo());
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
synchronized (TeaVMTestRunner.class) {
for (RunnerKindInfo info : runners.values()) {
if (info.runner != null) {
info.runner.stop();
info.runner.waitForCompletion();
}
}
}
}));
}
public TeaVMTestRunner(Class<?> testClass) throws InitializationError {
this.testClass = testClass;
classLoader = TeaVMTestRunner.class.getClassLoader(); classLoader = TeaVMTestRunner.class.getClassLoader();
classSource = getClassSource(classLoader); classSource = getClassSource(classLoader);
String outputPath = System.getProperty(PATH_PARAM); String outputPath = System.getProperty(PATH_PARAM);
if (outputPath != null) { if (outputPath != null) {
outputDir = new File(outputPath); outputDir = new File(outputPath);
@ -170,34 +144,31 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (runStrategyName != null) { if (runStrategyName != null) {
TestRunStrategy jsRunStrategy; TestRunStrategy jsRunStrategy;
switch (runStrategyName) { switch (runStrategyName) {
case "htmlunit":
jsRunStrategy = new HtmlUnitRunStrategy();
break;
case "browser": case "browser":
jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::customBrowser); jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::customBrowser);
break; break;
case "browser-chrome": case "browser-chrome":
jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::chromeBrowser); jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::chromeBrowser);
break; break;
case "browser-firefox": case "browser-firefox":
jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::firefoxBrowser); jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::firefoxBrowser);
break; break;
case "none": case "none":
jsRunStrategy = null; jsRunStrategy = null;
break; break;
default: default:
throw new InitializationError("Unknown run strategy: " + runStrategyName); throw new RuntimeException("Unknown run strategy: " + runStrategyName);
} }
runners.get(RunKind.JAVASCRIPT).strategy = jsRunStrategy; runners.put(RunKind.JAVASCRIPT, jsRunStrategy);
} }
String cCommand = System.getProperty(C_COMPILER); String cCommand = System.getProperty(C_COMPILER);
if (cCommand != null) { if (cCommand != null) {
runners.get(RunKind.C).strategy = new CRunStrategy(cCommand); runners.put(RunKind.C, new CRunStrategy(cCommand));
} }
String wasiCommand = System.getProperty(WASI_RUNNER); String wasiCommand = System.getProperty(WASI_RUNNER);
if (wasiCommand != null) { if (wasiCommand != null) {
runners.get(RunKind.WASI).strategy = new WasiRunStrategy(wasiCommand); runners.put(RunKind.WASI, new WasiRunStrategy(wasiCommand));
} }
runStrategyName = System.getProperty(WASM_RUNNER); runStrategyName = System.getProperty(WASM_RUNNER);
@ -205,28 +176,42 @@ public class TeaVMTestRunner extends Runner implements Filterable {
TestRunStrategy wasmRunStrategy; TestRunStrategy wasmRunStrategy;
switch (runStrategyName) { switch (runStrategyName) {
case "browser": case "browser":
wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", this::customBrowser); wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::customBrowser);
break; break;
case "chrome": case "chrome":
case "browser-chrome": case "browser-chrome":
wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", this::chromeBrowser); wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::chromeBrowser);
break; break;
case "browser-firefox": case "browser-firefox":
wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", this::firefoxBrowser); wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::firefoxBrowser);
break; break;
default: default:
throw new InitializationError("Unknown run strategy: " + runStrategyName); throw new RuntimeException("Unknown run strategy: " + runStrategyName);
} }
runners.get(RunKind.WASM).strategy = wasmRunStrategy; runners.put(RunKind.WASM, wasmRunStrategy);
} }
for (var strategy : runners.values()) {
strategy.beforeAll();
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
for (var strategy : runners.values()) {
strategy.afterAll();
}
}));
} }
private Process customBrowser(String url) { public TeaVMTestRunner(Class<?> testClass) throws InitializationError {
this.testClass = testClass;
}
private static Process customBrowser(String url) {
System.out.println("Open link to run tests: " + url + "?logging=true"); System.out.println("Open link to run tests: " + url + "?logging=true");
return null; return null;
} }
private Process chromeBrowser(String url) { private static Process chromeBrowser(String url) {
return browserTemplate("chrome", url, (profile, params) -> { return browserTemplate("chrome", url, (profile, params) -> {
addChromeCommand(params); addChromeCommand(params);
params.addAll(Arrays.asList( params.addAll(Arrays.asList(
@ -239,7 +224,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}); });
} }
private Process firefoxBrowser(String url) { private static Process firefoxBrowser(String url) {
return browserTemplate("firefox", url, (profile, params) -> { return browserTemplate("firefox", url, (profile, params) -> {
addFirefoxCommand(params); addFirefoxCommand(params);
params.addAll(Arrays.asList( params.addAll(Arrays.asList(
@ -250,7 +235,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}); });
} }
private void addChromeCommand(List<String> params) { private static void addChromeCommand(List<String> params) {
if (isMacos()) { if (isMacos()) {
params.add("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); params.add("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome");
} else if (isWindows()) { } else if (isWindows()) {
@ -263,7 +248,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
private void addFirefoxCommand(List<String> params) { private static void addFirefoxCommand(List<String> params) {
if (isMacos()) { if (isMacos()) {
params.add("/Applications/Firefox.app/Contents/MacOS/firefox"); params.add("/Applications/Firefox.app/Contents/MacOS/firefox");
return; return;
@ -276,15 +261,15 @@ public class TeaVMTestRunner extends Runner implements Filterable {
params.add("firefox"); params.add("firefox");
} }
private boolean isWindows() { private static boolean isWindows() {
return System.getProperty("os.name").toLowerCase().startsWith("windows"); return System.getProperty("os.name").toLowerCase().startsWith("windows");
} }
private boolean isMacos() { private static boolean isMacos() {
return System.getProperty("os.name").toLowerCase().startsWith("mac"); return System.getProperty("os.name").toLowerCase().startsWith("mac");
} }
private Process browserTemplate(String name, String url, BiConsumer<String, List<String>> paramsBuilder) { private static Process browserTemplate(String name, String url, BiConsumer<String, List<String>> paramsBuilder) {
File temp; File temp;
try { try {
temp = File.createTempFile("teavm", "teavm"); temp = File.createTempFile("teavm", "teavm");
@ -315,7 +300,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
private void logStream(InputStream stream, String name) { private static void logStream(InputStream stream, String name) {
new Thread(() -> { new Thread(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
while (true) { while (true) {
@ -331,7 +316,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}).start(); }).start();
} }
private void deleteDir(File dir) { private static void deleteDir(File dir) {
for (File file : dir.listFiles()) { for (File file : dir.listFiles()) {
if (file.isDirectory()) { if (file.isDirectory()) {
deleteDir(file); deleteDir(file);
@ -356,9 +341,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
@Override @Override
public void run(RunNotifier notifier) { public void run(RunNotifier notifier) {
List<Method> children = getFilteredChildren(); List<Method> children = getFilteredChildren();
latch = new CountDownLatch(children.size()); var description = getDescription();
notifier.fireTestStarted(getDescription()); notifier.fireTestStarted(description);
isWholeClassCompilation = testClass.isAnnotationPresent(WholeClassCompilation.class); isWholeClassCompilation = testClass.isAnnotationPresent(WholeClassCompilation.class);
if (isWholeClassCompilation) { if (isWholeClassCompilation) {
classCompilationOk = compileWholeClass(children, notifier); classCompilationOk = compileWholeClass(children, notifier);
@ -370,16 +355,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
writeRunsDescriptor(); writeRunsDescriptor();
runsInCurrentClass.clear(); runsInCurrentClass.clear();
while (true) { notifier.fireTestFinished(description);
try {
if (latch.await(1000, TimeUnit.MILLISECONDS)) {
break;
}
} catch (InterruptedException e) {
break;
}
}
notifier.fireTestFinished(getDescription());
} }
private List<Method> getChildren() { private List<Method> getChildren() {
@ -473,7 +449,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (isIgnored(child)) { if (isIgnored(child)) {
notifier.fireTestIgnored(description); notifier.fireTestIgnored(description);
latch.countDown(); notifier.fireTestFinished(description);
return; return;
} }
@ -488,42 +464,38 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
if (success && outputDir != null) { if (success && outputDir != null) {
int[] configurationIndex = new int[] { 0 };
List<TestRun> runs = new ArrayList<>(); List<TestRun> runs = new ArrayList<>();
Consumer<Boolean> onSuccess = runSuccess -> {
if (runSuccess && configurationIndex[0] < runs.size()) {
submitRun(runs.get(configurationIndex[0]++));
} else {
notifier.fireTestFinished(description);
latch.countDown();
}
};
if (isWholeClassCompilation) { if (isWholeClassCompilation) {
if (!classCompilationOk) { if (!classCompilationOk) {
notifier.fireTestFinished(description); notifier.fireTestFinished(description);
notifier.fireTestFailure(new Failure(description, notifier.fireTestFailure(new Failure(description,
new AssertionError("Could not compile test class"))); new AssertionError("Could not compile test class")));
latch.countDown();
} else { } else {
runTestsFromWholeClass(child, notifier, runs, onSuccess); prepareTestsFromWholeClass(child, runs);
onSuccess.accept(true);
} }
} else { } else {
runCompiledTest(child, notifier, runs, onSuccess); prepareCompiledTest(child, notifier, runs);
} }
for (var run : runs) {
try {
submitRun(run);
} catch (Throwable e) {
notifier.fireTestFailure(new Failure(description, e));
break;
}
}
notifier.fireTestFinished(description);
} else { } else {
if (!ran) { if (!ran) {
notifier.fireTestIgnored(description); notifier.fireTestIgnored(description);
} }
notifier.fireTestFinished(description); notifier.fireTestFinished(description);
latch.countDown();
} }
} }
private void runTestsFromWholeClass(Method child, RunNotifier notifier, List<TestRun> runs, private void prepareTestsFromWholeClass(Method child, List<TestRun> runs) {
Consumer<Boolean> onSuccess) {
File outputPath = getOutputPathForClass(); File outputPath = getOutputPathForClass();
File outputPathForMethod = getOutputPath(child); File outputPathForMethod = getOutputPath(child);
MethodDescriptor descriptor = getDescriptor(child); MethodDescriptor descriptor = getDescriptor(child);
@ -533,10 +505,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
testFilePath.mkdirs(); testFilePath.mkdirs();
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
for (TeaVMTestConfiguration<JavaScriptTarget> configuration : getJavaScriptConfigurations()) { for (var configuration : getJavaScriptConfigurations()) {
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".js"); File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".js");
runs.add(createTestRun(configuration, testPath, child, RunKind.JAVASCRIPT, reference.toString(), runs.add(createTestRun(configuration, testPath, child, RunKind.JAVASCRIPT, reference.toString()));
notifier, onSuccess));
File htmlPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".html"); File htmlPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".html");
properties.put("SCRIPT", "../" + testPath.getName()); properties.put("SCRIPT", "../" + testPath.getName());
properties.put("IDENTIFIER", reference.toString()); properties.put("IDENTIFIER", reference.toString());
@ -547,10 +518,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) { for (var configuration : getWasmConfigurations()) {
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm"); File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm");
runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString(), runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString()));
notifier, onSuccess));
File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html");
properties.put("SCRIPT", "../" + testPath.getName()); properties.put("SCRIPT", "../" + testPath.getName());
properties.put("IDENTIFIER", reference.toString()); properties.put("IDENTIFIER", reference.toString());
@ -561,30 +531,28 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasiConfigurations()) { for (var configuration : getWasiConfigurations()) {
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm"); File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm");
runs.add(createTestRun(configuration, testPath, child, RunKind.WASI, reference.toString(), runs.add(createTestRun(configuration, testPath, child, RunKind.WASI, reference.toString()));
notifier, onSuccess));
File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html");
properties.put("SCRIPT", "../" + testPath.getName()); properties.put("SCRIPT", "../" + testPath.getName());
properties.put("IDENTIFIER", reference.toString()); properties.put("IDENTIFIER", reference.toString());
} }
for (TeaVMTestConfiguration<CTarget> configuration : getCConfigurations()) { for (var 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()));
notifier, onSuccess));
} }
} }
private void runCompiledTest(Method child, RunNotifier notifier, List<TestRun> runs, Consumer<Boolean> onSuccess) { private void prepareCompiledTest(Method child, RunNotifier notifier, List<TestRun> runs) {
try { try {
File outputPath = getOutputPath(child); File outputPath = getOutputPath(child);
Map<String, String> properties = new HashMap<>(); Map<String, String> properties = new HashMap<>();
for (TeaVMTestConfiguration<JavaScriptTarget> configuration : getJavaScriptConfigurations()) { for (var configuration : getJavaScriptConfigurations()) {
CompileResult compileResult = compileToJs(singleTest(child), "test", configuration, outputPath); CompileResult compileResult = compileToJs(singleTest(child), "test", configuration, outputPath);
TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.JAVASCRIPT, onSuccess); TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.JAVASCRIPT);
if (run != null) { if (run != null) {
runs.add(run); runs.add(run);
@ -601,18 +569,18 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
for (TeaVMTestConfiguration<CTarget> configuration : getCConfigurations()) { for (var configuration : getCConfigurations()) {
CompileResult compileResult = compileToC(singleTest(child), "test", configuration, outputPath); CompileResult compileResult = compileToC(singleTest(child), "test", configuration, outputPath);
TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.C, onSuccess); TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.C);
if (run != null) { if (run != null) {
runs.add(run); runs.add(run);
} }
} }
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) { for (var configuration : getWasmConfigurations()) {
CompileResult compileResult = compileToWasm(WasmRuntimeType.TEAVM, singleTest(child), CompileResult compileResult = compileToWasm(WasmRuntimeType.TEAVM, singleTest(child),
"test", configuration, outputPath); "test", configuration, outputPath);
TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASM, onSuccess); TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASM);
if (run != null) { if (run != null) {
runs.add(run); runs.add(run);
@ -629,10 +597,10 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasiConfigurations()) { for (var configuration : getWasiConfigurations()) {
CompileResult compileResult = compileToWasm(WasmRuntimeType.WASI, singleTest(child), "test", CompileResult compileResult = compileToWasm(WasmRuntimeType.WASI, singleTest(child), "test",
configuration, outputPath); configuration, outputPath);
TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASI, onSuccess); TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASI);
if (run != null) { if (run != null) {
runs.add(run); runs.add(run);
@ -644,11 +612,8 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} 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));
latch.countDown();
return; return;
} }
onSuccess.accept(true);
} }
static String[] getExpectedExceptions(MethodReader method) { static String[] getExpectedExceptions(MethodReader method) {
@ -974,38 +939,22 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
private TestRun prepareRun(TeaVMTestConfiguration<?> configuration, Method child, CompileResult result, private TestRun prepareRun(TeaVMTestConfiguration<?> configuration, Method child, CompileResult result,
RunNotifier notifier, RunKind kind, Consumer<Boolean> onComplete) { RunNotifier notifier, RunKind kind) {
Description description = describeChild(child); Description description = describeChild(child);
if (!result.success) { if (!result.success) {
notifier.fireTestFailure(createFailure(description, result)); notifier.fireTestFailure(createFailure(description, result));
notifier.fireTestFinished(description); notifier.fireTestFinished(description);
latch.countDown();
return null; return null;
} }
return createTestRun(configuration, result.file, child, kind, null, notifier, onComplete); return createTestRun(configuration, result.file, child, kind, null);
} }
private TestRun createTestRun(TeaVMTestConfiguration<?> configuration, File file, Method child, RunKind kind, private TestRun createTestRun(TeaVMTestConfiguration<?> configuration, File file, Method child, RunKind kind,
String argument, RunNotifier notifier, Consumer<Boolean> onComplete) { String argument) {
Description description = describeChild(child); return new TestRun(generateName(child.getName(), configuration), file.getParentFile(), child,
file.getName(), kind, argument);
TestRunCallback callback = new TestRunCallback() {
@Override
public void complete() {
onComplete.accept(true);
}
@Override
public void error(Throwable e) {
notifier.fireTestFailure(new Failure(description, e));
onComplete.accept(false);
}
};
return new TestRun(generateName(child.getName(), configuration), file.getParentFile(), child, description,
file.getName(), kind, argument, callback);
} }
private String generateName(String baseName, TeaVMTestConfiguration<?> configuration) { private String generateName(String baseName, TeaVMTestConfiguration<?> configuration) {
@ -1024,27 +973,15 @@ public class TeaVMTestRunner extends Runner implements Filterable {
return new Failure(description, throwable); return new Failure(description, throwable);
} }
private void submitRun(TestRun run) { private boolean submitRun(TestRun run) throws IOException {
synchronized (TeaVMTestRunner.class) { runsInCurrentClass.add(run);
runsInCurrentClass.add(run); var strategy = runners.get(run.getKind());
RunnerKindInfo info = runners.get(run.getKind()); if (strategy == null) {
return false;
if (info.strategy == null) {
run.getCallback().complete();
return;
}
if (info.runner == null) {
info.runner = new TestRunner(info.strategy);
try {
info.runner.setNumThreads(Integer.parseInt(System.getProperty(THREAD_COUNT, "1")));
} catch (NumberFormatException e) {
info.runner.setNumThreads(1);
}
info.runner.init();
}
info.runner.run(run);
} }
strategy.runTest(run);
return true;
} }
private File getOutputPath(Method method) { private File getOutputPath(Method method) {
@ -1422,9 +1359,8 @@ public class TeaVMTestRunner extends Runner implements Filterable {
return sb.append(s.substring(i)).toString(); return sb.append(s.substring(i)).toString();
} }
private ClassHolderSource getClassSource(ClassLoader classLoader) { private static ClassHolderSource getClassSource(ClassLoader classLoader) {
return classSources.computeIfAbsent(classLoader, cl -> new PreOptimizingClassHolderSource( return new PreOptimizingClassHolderSource(new ClasspathClassHolderSource(classLoader, referenceCache));
new ClasspathClassHolderSource(classLoader, referenceCache)));
} }
@Override @Override

View File

@ -30,7 +30,7 @@ final class TestNativeEntryPoint {
} catch (Throwable e) { } catch (Throwable e) {
PrintStream out = new PrintStream(StderrOutputStream.INSTANCE); PrintStream out = new PrintStream(StderrOutputStream.INSTANCE);
e.printStackTrace(out); e.printStackTrace(out);
out.println("FAILURE"); new PrintStream(StdoutOutputStream.INSTANCE).println("FAILURE");
} }
} }
} }

View File

@ -17,28 +17,23 @@ package org.teavm.junit;
import java.io.File; import java.io.File;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import org.junit.runner.Description;
class TestRun { class TestRun {
private String name; private String name;
private File baseDirectory; private File baseDirectory;
private Method method; private Method method;
private Description description;
private String fileName; private String fileName;
private RunKind kind; private RunKind kind;
private TestRunCallback callback;
private String argument; private String argument;
TestRun(String name, File baseDirectory, Method method, Description description, String fileName, RunKind kind, TestRun(String name, File baseDirectory, Method method, String fileName, RunKind kind,
String argument, TestRunCallback callback) { String argument) {
this.name = name; this.name = name;
this.baseDirectory = baseDirectory; this.baseDirectory = baseDirectory;
this.method = method; this.method = method;
this.description = description;
this.fileName = fileName; this.fileName = fileName;
this.kind = kind; this.kind = kind;
this.argument = argument; this.argument = argument;
this.callback = callback;
} }
public String getName() { public String getName() {
@ -53,10 +48,6 @@ class TestRun {
return method; return method;
} }
public Description getDescription() {
return description;
}
public String getFileName() { public String getFileName() {
return fileName; return fileName;
} }
@ -68,8 +59,4 @@ class TestRun {
public String getArgument() { public String getArgument() {
return argument; return argument;
} }
public TestRunCallback getCallback() {
return callback;
}
} }

View File

@ -18,13 +18,11 @@ package org.teavm.junit;
import java.io.IOException; import java.io.IOException;
interface TestRunStrategy { interface TestRunStrategy {
void beforeAll(); default void beforeAll() {
}
void afterAll(); default void afterAll() {
}
void beforeThread();
void afterThread();
void runTest(TestRun run) throws IOException; void runTest(TestRun run) throws IOException;
} }

View File

@ -1,100 +0,0 @@
/*
* Copyright 2016 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.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
class TestRunner {
private int numThreads = 1;
private TestRunStrategy strategy;
private BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
private CountDownLatch latch;
private volatile boolean stopped;
TestRunner(TestRunStrategy strategy) {
this.strategy = strategy;
}
public void setNumThreads(int numThreads) {
this.numThreads = numThreads;
}
public void init() {
latch = new CountDownLatch(numThreads);
strategy.beforeAll();
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
strategy.afterAll();
}));
for (int i = 0; i < numThreads; ++i) {
Thread thread = new Thread(() -> {
strategy.beforeThread();
while (!stopped || !taskQueue.isEmpty()) {
Runnable task;
try {
task = taskQueue.poll(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
break;
}
if (task != null) {
task.run();
}
}
strategy.afterThread();
latch.countDown();
});
thread.setDaemon(true);
thread.setName("teavm-test-runner-" + i);
thread.start();
}
}
private void addTask(Runnable runnable) {
taskQueue.add(runnable);
}
public void stop() {
stopped = true;
taskQueue.add(() -> { });
}
public void waitForCompletion() {
while (true) {
try {
if (latch.await(1000, TimeUnit.MILLISECONDS)) {
break;
}
} catch (InterruptedException e) {
break;
}
}
}
public void run(TestRun run) {
addTask(() -> runImpl(run));
}
private void runImpl(TestRun run) {
try {
strategy.runTest(run);
} catch (Exception e) {
run.getCallback().error(e);
}
}
}

View File

@ -30,22 +30,6 @@ class WasiRunStrategy implements TestRunStrategy {
this.runCommand = runCommand; this.runCommand = runCommand;
} }
@Override
public void beforeAll() {
}
@Override
public void afterAll() {
}
@Override
public void beforeThread() {
}
@Override
public void afterThread() {
}
@Override @Override
public void runTest(TestRun run) throws IOException { public void runTest(TestRun run) throws IOException {
try { try {
@ -62,12 +46,11 @@ class WasiRunStrategy implements TestRunStrategy {
} }
if (!stdout.isEmpty() && stdout.get(stdout.size() - 1).equals("SUCCESS")) { if (!stdout.isEmpty() && stdout.get(stdout.size() - 1).equals("SUCCESS")) {
writeLines(runtimeOutput); writeLines(runtimeOutput);
run.getCallback().complete();
} else { } else {
run.getCallback().error(new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput))); throw new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput));
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
run.getCallback().complete(); Thread.currentThread().interrupt();
} }
} }

View File

@ -1,28 +0,0 @@
var $rt_decodeStack;
function runMain(argument, stackDecoder, callback) {
$rt_decodeStack = stackDecoder;
main(argument !== null ? [argument] : [], function(result) {
var message = {};
if (result instanceof Error) {
makeErrorMessage(message, result);
} else {
message.status = "ok";
}
callback.complete(message);
});
function makeErrorMessage(message, e) {
message.status = "exception";
var je = main.javaException(e);
if (je) {
message.className = je.constructor.name;
message.message = je.getMessage();
} else {
message.className = Object.getPrototypeOf(e).name;
message.message = e.message;
}
message.exception = e;
message.stack = e.stack;
}
}

View File

@ -91,6 +91,7 @@ function launchTest(argument, callback) {
function launchWasmTest(path, argument, callback) { function launchWasmTest(path, argument, callback) {
let output = []; let output = [];
let outputBuffer = ""; let outputBuffer = "";
let outputBufferStderr = "";
function putwchar(charCode) { function putwchar(charCode) {
if (charCode === 10) { if (charCode === 10) {
@ -106,6 +107,7 @@ function launchWasmTest(path, argument, callback) {
break; break;
default: default:
output.push(outputBuffer); output.push(outputBuffer);
log.push({ message: outputBuffer, type: "stdout" });
outputBuffer = ""; outputBuffer = "";
} }
} else { } else {
@ -113,8 +115,37 @@ function launchWasmTest(path, argument, callback) {
} }
} }
function putwchars(controller, buffer, count) {
let memory = new Int8Array(instance.exports.memory.buffer);
for (let i = 0; i < count; ++i) {
// TODO: support UTF-8
putwchar(memory[buffer++]);
}
}
function putwcharStderr(charCode) {
if (charCode === 10) {
log.push({ message: outputBufferStderr, type: "stderr" });
outputBufferStderr = "";
} else {
outputBufferStderr += String.fromCharCode(charCode);
}
}
function putwcharsStderr(controller, buffer, count) {
let memory = new Int8Array(instance.exports.memory.buffer);
for (let i = 0; i < count; ++i) {
// TODO: support UTF-8
putwcharStderr(memory[buffer++]);
}
}
let instance = null;
TeaVM.wasm.load(path, { TeaVM.wasm.load(path, {
installImports: function(o) { installImports: function(o) {
o.teavm.putwcharsOut = (chars, count) => putwchars(instance, chars, count);
o.teavm.putwcharsErr = (chars, count) => putwcharsStderr(instance, chars, count);
o.teavm.putwchar = putwchar; o.teavm.putwchar = putwchar;
}, },
errorCallback: function(err) { errorCallback: function(err) {
@ -124,12 +155,9 @@ function launchWasmTest(path, argument, callback) {
})); }));
} }
}).then(teavm => { }).then(teavm => {
teavm.main(argument ? [argument] : []); instance = teavm.instance;
}) return teavm.main(argument ? [argument] : []);
.then(() => { }).catch(err => {
callback(wrapResponse({ status: "OK" }));
})
.catch(err => {
callback(wrapResponse({ callback(wrapResponse({
status: "failed", status: "failed",
errorMessage: err.message + '\n' + err.stack errorMessage: err.message + '\n' + err.stack