diff --git a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java index ef1c0dacb..4f664c8d0 100644 --- a/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java +++ b/core/src/main/java/org/teavm/backend/wasm/runtime/WasmSupport.java @@ -23,10 +23,10 @@ public class WasmSupport { private WasmSupport() { } - @Import(name = "putwcharsOut", module = "teavm") + @Import(name = "putwcharsErr", module = "teavm") 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 long currentTimeMillis() { 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 index c3ea3b587..c84b1770b 100644 --- a/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js +++ b/core/src/main/resources/org/teavm/backend/wasm/wasm-runtime.js @@ -213,7 +213,12 @@ TeaVM.wasm = function() { controller.resolve = resolve; 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); }); } diff --git a/tests/build.gradle.kts b/tests/build.gradle.kts index 52305de71..b64025249 100644 --- a/tests/build.gradle.kts +++ b/tests/build.gradle.kts @@ -40,7 +40,6 @@ dependencies { tasks.test { 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() systemProperty("teavm.junit.js", providers.gradleProperty("teavm.tests.js").orElse("true").get()) diff --git a/tools/junit/build.gradle.kts b/tools/junit/build.gradle.kts index 8e5aa10d7..7683d1b3a 100644 --- a/tools/junit/build.gradle.kts +++ b/tools/junit/build.gradle.kts @@ -33,8 +33,6 @@ dependencies { implementation(project(":core")) implementation(project(":tools:core")) - implementation(libs.rhino) - implementation(libs.htmlunit) implementation(libs.jackson.annotations) implementation(libs.jackson.databind) implementation(libs.javax.servlet) diff --git a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java index 8a66de29c..f4ed56c6e 100644 --- a/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/BrowserRunStrategy.java @@ -114,34 +114,24 @@ class BrowserRunStrategy implements TestRunStrategy { } } - @Override - public void beforeThread() { - } - - @Override - public void afterThread() { - } - static class CallbackWrapper implements TestRunCallback { private final CountDownLatch latch; - private final TestRun run; + volatile Throwable error; volatile boolean shouldRepeat; - CallbackWrapper(CountDownLatch latch, TestRun run) { + CallbackWrapper(CountDownLatch latch) { this.latch = latch; - this.run = run; } @Override public void complete() { latch.countDown(); - run.getCallback().complete(); } @Override public void error(Throwable e) { + error = e; latch.countDown(); - run.getCallback().error(e); } void repeat() { @@ -164,14 +154,14 @@ class BrowserRunStrategy implements TestRunStrategy { ws = wsSessionQueue.poll(1, TimeUnit.SECONDS); } while (ws == null || !ws.isOpen()); } catch (InterruptedException e) { - run.getCallback().error(e); + Thread.currentThread().interrupt(); return true; } 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); JsonNodeFactory nf = objectMapper.getNodeFactory(); @@ -215,6 +205,15 @@ class BrowserRunStrategy implements TestRunStrategy { 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; } diff --git a/tools/junit/src/main/java/org/teavm/junit/CRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/CRunStrategy.java index dd2a3ab76..858d7def8 100644 --- a/tools/junit/src/main/java/org/teavm/junit/CRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/CRunStrategy.java @@ -33,22 +33,6 @@ class CRunStrategy implements TestRunStrategy { this.compilerCommand = compilerCommand; } - @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 { @@ -60,8 +44,7 @@ class CRunStrategy implements TestRunStrategy { File outputFile = new File(run.getBaseDirectory(), exeName); boolean compilerSuccess = compile(run.getBaseDirectory()); if (!compilerSuccess) { - run.getCallback().error(new RuntimeException("C compiler error")); - return; + throw new RuntimeException("C compiler error"); } List runtimeOutput = new ArrayList<>(); @@ -77,12 +60,11 @@ class CRunStrategy implements TestRunStrategy { } 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))); + throw new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput)); } } catch (InterruptedException e) { - run.getCallback().complete(); + Thread.currentThread().interrupt(); } } diff --git a/tools/junit/src/main/java/org/teavm/junit/HtmlUnitRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/HtmlUnitRunStrategy.java deleted file mode 100644 index eff3c9620..000000000 --- a/tools/junit/src/main/java/org/teavm/junit/HtmlUnitRunStrategy.java +++ /dev/null @@ -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 = new ThreadLocal<>(); - private ThreadLocal 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; - } - } - } -} 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 dc81833de..8ac400511 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -40,9 +40,6 @@ import java.util.Map; import java.util.Properties; import java.util.Set; 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.Consumer; import java.util.function.Supplier; @@ -123,44 +120,21 @@ public class TeaVMTestRunner extends Runner implements Filterable { private Class testClass; private boolean isWholeClassCompilation; - private ClassHolderSource classSource; - private ClassLoader classLoader; + private static ClassHolderSource classSource; + private static ClassLoader classLoader; private Description suiteDescription; - private static Map classSources = new WeakHashMap<>(); - private File outputDir; + private static File outputDir; private Map descriptions = new HashMap<>(); - private static Map runners = new HashMap<>(); - private CountDownLatch latch; + private static Map runners = new HashMap<>(); private List filteredChildren; - private ReferenceCache referenceCache = new ReferenceCache(); + private static ReferenceCache referenceCache = new ReferenceCache(); private boolean classCompilationOk; private List runsInCurrentClass = new ArrayList<>(); - static class RunnerKindInfo { - volatile TestRunner runner; - volatile TestRunStrategy strategy; - } - 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(); classSource = getClassSource(classLoader); + String outputPath = System.getProperty(PATH_PARAM); if (outputPath != null) { outputDir = new File(outputPath); @@ -170,34 +144,31 @@ public class TeaVMTestRunner extends Runner implements Filterable { if (runStrategyName != null) { TestRunStrategy jsRunStrategy; switch (runStrategyName) { - case "htmlunit": - jsRunStrategy = new HtmlUnitRunStrategy(); - break; case "browser": - jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::customBrowser); + jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::customBrowser); break; case "browser-chrome": - jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::chromeBrowser); + jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::chromeBrowser); break; case "browser-firefox": - jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", this::firefoxBrowser); + jsRunStrategy = new BrowserRunStrategy(outputDir, "JAVASCRIPT", TeaVMTestRunner::firefoxBrowser); break; case "none": jsRunStrategy = null; break; 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); if (cCommand != null) { - runners.get(RunKind.C).strategy = new CRunStrategy(cCommand); + runners.put(RunKind.C, new CRunStrategy(cCommand)); } String wasiCommand = System.getProperty(WASI_RUNNER); if (wasiCommand != null) { - runners.get(RunKind.WASI).strategy = new WasiRunStrategy(wasiCommand); + runners.put(RunKind.WASI, new WasiRunStrategy(wasiCommand)); } runStrategyName = System.getProperty(WASM_RUNNER); @@ -205,28 +176,42 @@ public class TeaVMTestRunner extends Runner implements Filterable { TestRunStrategy wasmRunStrategy; switch (runStrategyName) { case "browser": - wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", this::customBrowser); + wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::customBrowser); break; case "chrome": case "browser-chrome": - wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", this::chromeBrowser); + wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::chromeBrowser); break; case "browser-firefox": - wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", this::firefoxBrowser); + wasmRunStrategy = new BrowserRunStrategy(outputDir, "WASM", TeaVMTestRunner::firefoxBrowser); break; 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"); return null; } - private Process chromeBrowser(String url) { + private static Process chromeBrowser(String url) { return browserTemplate("chrome", url, (profile, params) -> { addChromeCommand(params); 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) -> { addFirefoxCommand(params); params.addAll(Arrays.asList( @@ -250,7 +235,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { }); } - private void addChromeCommand(List params) { + private static void addChromeCommand(List params) { if (isMacos()) { params.add("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"); } else if (isWindows()) { @@ -263,7 +248,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { } } - private void addFirefoxCommand(List params) { + private static void addFirefoxCommand(List params) { if (isMacos()) { params.add("/Applications/Firefox.app/Contents/MacOS/firefox"); return; @@ -276,15 +261,15 @@ public class TeaVMTestRunner extends Runner implements Filterable { params.add("firefox"); } - private boolean isWindows() { + private static boolean isWindows() { return System.getProperty("os.name").toLowerCase().startsWith("windows"); } - private boolean isMacos() { + private static boolean isMacos() { return System.getProperty("os.name").toLowerCase().startsWith("mac"); } - private Process browserTemplate(String name, String url, BiConsumer> paramsBuilder) { + private static Process browserTemplate(String name, String url, BiConsumer> paramsBuilder) { File temp; try { 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(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { while (true) { @@ -331,7 +316,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { }).start(); } - private void deleteDir(File dir) { + private static void deleteDir(File dir) { for (File file : dir.listFiles()) { if (file.isDirectory()) { deleteDir(file); @@ -356,9 +341,9 @@ public class TeaVMTestRunner extends Runner implements Filterable { @Override public void run(RunNotifier notifier) { List children = getFilteredChildren(); - latch = new CountDownLatch(children.size()); + var description = getDescription(); - notifier.fireTestStarted(getDescription()); + notifier.fireTestStarted(description); isWholeClassCompilation = testClass.isAnnotationPresent(WholeClassCompilation.class); if (isWholeClassCompilation) { classCompilationOk = compileWholeClass(children, notifier); @@ -370,16 +355,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { writeRunsDescriptor(); runsInCurrentClass.clear(); - while (true) { - try { - if (latch.await(1000, TimeUnit.MILLISECONDS)) { - break; - } - } catch (InterruptedException e) { - break; - } - } - notifier.fireTestFinished(getDescription()); + notifier.fireTestFinished(description); } private List getChildren() { @@ -473,7 +449,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { if (isIgnored(child)) { notifier.fireTestIgnored(description); - latch.countDown(); + notifier.fireTestFinished(description); return; } @@ -488,42 +464,38 @@ public class TeaVMTestRunner extends Runner implements Filterable { } if (success && outputDir != null) { - int[] configurationIndex = new int[] { 0 }; - List runs = new ArrayList<>(); - Consumer onSuccess = runSuccess -> { - if (runSuccess && configurationIndex[0] < runs.size()) { - submitRun(runs.get(configurationIndex[0]++)); - } else { - notifier.fireTestFinished(description); - latch.countDown(); - } - }; if (isWholeClassCompilation) { if (!classCompilationOk) { notifier.fireTestFinished(description); notifier.fireTestFailure(new Failure(description, new AssertionError("Could not compile test class"))); - latch.countDown(); } else { - runTestsFromWholeClass(child, notifier, runs, onSuccess); - onSuccess.accept(true); + prepareTestsFromWholeClass(child, runs); } } 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 { if (!ran) { notifier.fireTestIgnored(description); } notifier.fireTestFinished(description); - latch.countDown(); } } - private void runTestsFromWholeClass(Method child, RunNotifier notifier, List runs, - Consumer onSuccess) { + private void prepareTestsFromWholeClass(Method child, List runs) { File outputPath = getOutputPathForClass(); File outputPathForMethod = getOutputPath(child); MethodDescriptor descriptor = getDescriptor(child); @@ -533,10 +505,9 @@ public class TeaVMTestRunner extends Runner implements Filterable { testFilePath.mkdirs(); Map properties = new HashMap<>(); - for (TeaVMTestConfiguration configuration : getJavaScriptConfigurations()) { + for (var configuration : getJavaScriptConfigurations()) { File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".js"); - runs.add(createTestRun(configuration, testPath, child, RunKind.JAVASCRIPT, reference.toString(), - notifier, onSuccess)); + runs.add(createTestRun(configuration, testPath, child, RunKind.JAVASCRIPT, reference.toString())); File htmlPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".html"); properties.put("SCRIPT", "../" + testPath.getName()); properties.put("IDENTIFIER", reference.toString()); @@ -547,10 +518,9 @@ public class TeaVMTestRunner extends Runner implements Filterable { } } - for (TeaVMTestConfiguration configuration : getWasmConfigurations()) { + for (var configuration : getWasmConfigurations()) { File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm"); - runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString(), - notifier, onSuccess)); + runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString())); File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); properties.put("SCRIPT", "../" + testPath.getName()); properties.put("IDENTIFIER", reference.toString()); @@ -561,30 +531,28 @@ public class TeaVMTestRunner extends Runner implements Filterable { } } - for (TeaVMTestConfiguration configuration : getWasiConfigurations()) { + for (var configuration : getWasiConfigurations()) { File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm"); - runs.add(createTestRun(configuration, testPath, child, RunKind.WASI, reference.toString(), - notifier, onSuccess)); + runs.add(createTestRun(configuration, testPath, child, RunKind.WASI, reference.toString())); File htmlPath = getOutputFile(outputPathForMethod, "test-wasm", configuration.getSuffix(), false, ".html"); properties.put("SCRIPT", "../" + testPath.getName()); properties.put("IDENTIFIER", reference.toString()); } - for (TeaVMTestConfiguration configuration : getCConfigurations()) { + for (var configuration : getCConfigurations()) { File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), true, ".c"); - runs.add(createTestRun(configuration, testPath, child, RunKind.C, reference.toString(), - notifier, onSuccess)); + runs.add(createTestRun(configuration, testPath, child, RunKind.C, reference.toString())); } } - private void runCompiledTest(Method child, RunNotifier notifier, List runs, Consumer onSuccess) { + private void prepareCompiledTest(Method child, RunNotifier notifier, List runs) { try { File outputPath = getOutputPath(child); Map properties = new HashMap<>(); - for (TeaVMTestConfiguration configuration : getJavaScriptConfigurations()) { + for (var configuration : getJavaScriptConfigurations()) { 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) { runs.add(run); @@ -601,18 +569,18 @@ public class TeaVMTestRunner extends Runner implements Filterable { } } - for (TeaVMTestConfiguration configuration : getCConfigurations()) { + for (var configuration : getCConfigurations()) { 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) { runs.add(run); } } - for (TeaVMTestConfiguration configuration : getWasmConfigurations()) { + for (var configuration : getWasmConfigurations()) { CompileResult compileResult = compileToWasm(WasmRuntimeType.TEAVM, singleTest(child), "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) { runs.add(run); @@ -629,10 +597,10 @@ public class TeaVMTestRunner extends Runner implements Filterable { } } - for (TeaVMTestConfiguration configuration : getWasiConfigurations()) { + for (var configuration : getWasiConfigurations()) { CompileResult compileResult = compileToWasm(WasmRuntimeType.WASI, singleTest(child), "test", configuration, outputPath); - TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASI, onSuccess); + TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASI); if (run != null) { runs.add(run); @@ -644,11 +612,8 @@ public class TeaVMTestRunner extends Runner implements Filterable { } catch (Throwable e) { notifier.fireTestFailure(new Failure(describeChild(child), e)); notifier.fireTestFinished(describeChild(child)); - latch.countDown(); return; } - - onSuccess.accept(true); } 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, - RunNotifier notifier, RunKind kind, Consumer onComplete) { + RunNotifier notifier, RunKind kind) { Description description = describeChild(child); if (!result.success) { notifier.fireTestFailure(createFailure(description, result)); notifier.fireTestFinished(description); - latch.countDown(); 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, - String argument, RunNotifier notifier, Consumer onComplete) { - Description description = describeChild(child); - - 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); + String argument) { + return new TestRun(generateName(child.getName(), configuration), file.getParentFile(), child, + file.getName(), kind, argument); } private String generateName(String baseName, TeaVMTestConfiguration configuration) { @@ -1024,27 +973,15 @@ public class TeaVMTestRunner extends Runner implements Filterable { return new Failure(description, throwable); } - private void submitRun(TestRun run) { - synchronized (TeaVMTestRunner.class) { - runsInCurrentClass.add(run); - RunnerKindInfo info = runners.get(run.getKind()); - - 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); + private boolean submitRun(TestRun run) throws IOException { + runsInCurrentClass.add(run); + var strategy = runners.get(run.getKind()); + if (strategy == null) { + return false; } + + strategy.runTest(run); + return true; } private File getOutputPath(Method method) { @@ -1422,9 +1359,8 @@ public class TeaVMTestRunner extends Runner implements Filterable { return sb.append(s.substring(i)).toString(); } - private ClassHolderSource getClassSource(ClassLoader classLoader) { - return classSources.computeIfAbsent(classLoader, cl -> new PreOptimizingClassHolderSource( - new ClasspathClassHolderSource(classLoader, referenceCache))); + private static ClassHolderSource getClassSource(ClassLoader classLoader) { + return new PreOptimizingClassHolderSource(new ClasspathClassHolderSource(classLoader, referenceCache)); } @Override diff --git a/tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java index 508d31256..2180999ae 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestNativeEntryPoint.java @@ -30,7 +30,7 @@ final class TestNativeEntryPoint { } catch (Throwable e) { PrintStream out = new PrintStream(StderrOutputStream.INSTANCE); e.printStackTrace(out); - out.println("FAILURE"); + new PrintStream(StdoutOutputStream.INSTANCE).println("FAILURE"); } } } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestRun.java b/tools/junit/src/main/java/org/teavm/junit/TestRun.java index e7f460dc6..8a02cdde2 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestRun.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestRun.java @@ -17,28 +17,23 @@ package org.teavm.junit; import java.io.File; import java.lang.reflect.Method; -import org.junit.runner.Description; class TestRun { private String name; private File baseDirectory; private Method method; - private Description description; private String fileName; private RunKind kind; - private TestRunCallback callback; private String argument; - TestRun(String name, File baseDirectory, Method method, Description description, String fileName, RunKind kind, - String argument, TestRunCallback callback) { + TestRun(String name, File baseDirectory, Method method, String fileName, RunKind kind, + String argument) { this.name = name; this.baseDirectory = baseDirectory; this.method = method; - this.description = description; this.fileName = fileName; this.kind = kind; this.argument = argument; - this.callback = callback; } public String getName() { @@ -53,10 +48,6 @@ class TestRun { return method; } - public Description getDescription() { - return description; - } - public String getFileName() { return fileName; } @@ -68,8 +59,4 @@ class TestRun { public String getArgument() { return argument; } - - public TestRunCallback getCallback() { - return callback; - } } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/TestRunStrategy.java index 350ea9861..63666d594 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestRunStrategy.java @@ -18,13 +18,11 @@ package org.teavm.junit; import java.io.IOException; interface TestRunStrategy { - void beforeAll(); + default void beforeAll() { + } - void afterAll(); - - void beforeThread(); - - void afterThread(); + default void afterAll() { + } void runTest(TestRun run) throws IOException; } diff --git a/tools/junit/src/main/java/org/teavm/junit/TestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TestRunner.java deleted file mode 100644 index 594521f7d..000000000 --- a/tools/junit/src/main/java/org/teavm/junit/TestRunner.java +++ /dev/null @@ -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 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); - } - } -} diff --git a/tools/junit/src/main/java/org/teavm/junit/WasiRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/WasiRunStrategy.java index af110381d..f719cc061 100644 --- a/tools/junit/src/main/java/org/teavm/junit/WasiRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/WasiRunStrategy.java @@ -30,22 +30,6 @@ class WasiRunStrategy implements TestRunStrategy { 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 { @@ -62,12 +46,11 @@ class WasiRunStrategy implements TestRunStrategy { } 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))); + throw new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput)); } } catch (InterruptedException e) { - run.getCallback().complete(); + Thread.currentThread().interrupt(); } } diff --git a/tools/junit/src/main/resources/teavm-htmlunit-adapter.js b/tools/junit/src/main/resources/teavm-htmlunit-adapter.js deleted file mode 100644 index 61bc3d7a2..000000000 --- a/tools/junit/src/main/resources/teavm-htmlunit-adapter.js +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/tools/junit/src/main/resources/test-server/frame.js b/tools/junit/src/main/resources/test-server/frame.js index b35737426..c70834857 100644 --- a/tools/junit/src/main/resources/test-server/frame.js +++ b/tools/junit/src/main/resources/test-server/frame.js @@ -91,6 +91,7 @@ function launchTest(argument, callback) { function launchWasmTest(path, argument, callback) { let output = []; let outputBuffer = ""; + let outputBufferStderr = ""; function putwchar(charCode) { if (charCode === 10) { @@ -106,6 +107,7 @@ function launchWasmTest(path, argument, callback) { break; default: output.push(outputBuffer); + log.push({ message: outputBuffer, type: "stdout" }); outputBuffer = ""; } } 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, { 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; }, errorCallback: function(err) { @@ -124,12 +155,9 @@ function launchWasmTest(path, argument, callback) { })); } }).then(teavm => { - teavm.main(argument ? [argument] : []); - }) - .then(() => { - callback(wrapResponse({ status: "OK" })); - }) - .catch(err => { + instance = teavm.instance; + return teavm.main(argument ? [argument] : []); + }).catch(err => { callback(wrapResponse({ status: "failed", errorMessage: err.message + '\n' + err.stack