diff --git a/core/src/main/resources/org/teavm/backend/c/runtime.c b/core/src/main/resources/org/teavm/backend/c/runtime.c index 712126a6a..9c0721313 100644 --- a/core/src/main/resources/org/teavm/backend/c/runtime.c +++ b/core/src/main/resources/org/teavm/backend/c/runtime.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/tools/junit/src/main/java/org/teavm/junit/CRunner.java b/tools/junit/src/main/java/org/teavm/junit/CRunStrategy.java similarity index 87% rename from tools/junit/src/main/java/org/teavm/junit/CRunner.java rename to tools/junit/src/main/java/org/teavm/junit/CRunStrategy.java index 3893c80b9..98b3ea5b8 100644 --- a/tools/junit/src/main/java/org/teavm/junit/CRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/CRunStrategy.java @@ -17,18 +17,28 @@ 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; -public class CRunner { +class CRunStrategy implements TestRunStrategy { private String compilerCommand; - public CRunner(String compilerCommand) { + CRunStrategy(String compilerCommand) { this.compilerCommand = compilerCommand; } - public void run(TestRun run) { + @Override + public void beforeThread() { + } + + @Override + public void afterThread() { + } + + @Override + public void runTest(TestRun run) throws IOException { try { File inputFile = new File(run.getBaseDirectory(), run.getFileName()); String exeName = run.getFileName(); @@ -59,9 +69,8 @@ public class CRunner { } else { run.getCallback().error(new RuntimeException("Test failed:\n" + mergeLines(runtimeOutput))); } - } catch (Exception e) { - run.getCallback().error(e); - return; + } catch (InterruptedException e) { + run.getCallback().complete(); } } @@ -79,7 +88,8 @@ public class CRunner { } } - private boolean runCompiler(File inputFile, File outputFile, List output) throws Exception { + private boolean runCompiler(File inputFile, File outputFile, List output) + throws IOException, InterruptedException { String[] parts = compilerCommand.split(" +"); for (int i = 0; i < parts.length; ++i) { switch (parts[i]) { @@ -94,18 +104,18 @@ public class CRunner { return runProcess(new ProcessBuilder(parts).start(), output); } - private boolean runProcess(Process process, List output) throws Exception { + private boolean runProcess(Process process, List output) throws IOException, InterruptedException { BufferedReader stdin = new BufferedReader(new InputStreamReader(process.getInputStream())); BufferedReader stderr = new BufferedReader(new InputStreamReader(process.getErrorStream())); - while (process.isAlive()) { + while (true) { String line = stderr.readLine(); if (line == null) { break; } output.add(line); } - while (process.isAlive()) { + while (true) { String line = stdin.readLine(); if (line == null) { break; diff --git a/tools/junit/src/main/java/org/teavm/junit/HtmlUnitRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/HtmlUnitRunStrategy.java index d734fa213..4d2946540 100644 --- a/tools/junit/src/main/java/org/teavm/junit/HtmlUnitRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/HtmlUnitRunStrategy.java @@ -46,7 +46,7 @@ class HtmlUnitRunStrategy implements TestRunStrategy { } @Override - public String runTest(TestRun run) throws IOException { + public void runTest(TestRun run) throws IOException { if (++runs == 50) { runs = 0; cleanUp(); @@ -66,7 +66,7 @@ class HtmlUnitRunStrategy implements TestRunStrategy { .getJavaScriptResult(); Object[] args = new Object[] { new NativeJavaObject(function, asyncResult, AsyncResult.class) }; page.get().executeJavaScriptFunctionIfPossible(function, function, args, page.get()); - return (String) asyncResult.getResult(); + JavaScriptResultParser.parseResult((String) asyncResult.getResult(), run.getCallback()); } private void cleanUp() { diff --git a/tools/junit/src/main/java/org/teavm/junit/JavaScriptResultParser.java b/tools/junit/src/main/java/org/teavm/junit/JavaScriptResultParser.java new file mode 100644 index 000000000..f6ecaf4de --- /dev/null +++ b/tools/junit/src/main/java/org/teavm/junit/JavaScriptResultParser.java @@ -0,0 +1,46 @@ +/* + * 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 com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; + +final class JavaScriptResultParser { + private JavaScriptResultParser() { + } + + static void parseResult(String result, TestRunCallback callback) throws IOException { + if (result == null) { + callback.complete(); + return; + } + ObjectMapper mapper = new ObjectMapper(); + ObjectNode resultObject = (ObjectNode) mapper.readTree(result); + String status = resultObject.get("status").asText(); + switch (status) { + case "ok": + callback.complete(); + break; + case "exception": { + String stack = resultObject.get("stack").asText(); + String exception = resultObject.has("exception") ? resultObject.get("exception").asText() : null; + callback.error(new AssertionError(exception + "\n" + stack)); + break; + } + } + } +} diff --git a/tools/junit/src/main/java/org/teavm/junit/SeleniumRunStrategy.java b/tools/junit/src/main/java/org/teavm/junit/SeleniumRunStrategy.java index b460a1f73..a8caf7a3f 100644 --- a/tools/junit/src/main/java/org/teavm/junit/SeleniumRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/SeleniumRunStrategy.java @@ -52,7 +52,7 @@ class SeleniumRunStrategy implements TestRunStrategy { } @Override - public String runTest(TestRun run) throws IOException { + public void runTest(TestRun run) throws IOException { commandsSent.set(commandsSent.get() + 1); if (commandsSent.get().equals(100)) { commandsSent.set(0); @@ -63,8 +63,9 @@ class SeleniumRunStrategy implements TestRunStrategy { webDriver.get().manage().timeouts().setScriptTimeout(2, TimeUnit.SECONDS); JavascriptExecutor js = (JavascriptExecutor) webDriver.get(); + String result; try { - return (String) js.executeAsyncScript( + result = (String) js.executeAsyncScript( readResource("teavm-selenium.js"), readFile(new File(run.getBaseDirectory(), "runtime.js")), readFile(new File(run.getBaseDirectory(), run.getFileName())), @@ -78,8 +79,10 @@ class SeleniumRunStrategy implements TestRunStrategy { run.getCallback().error(new AssertionError(error)); } } - return null; + return; } + + JavaScriptResultParser.parseResult(result, run.getCallback()); } private String readFile(File file) throws IOException { 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 3134d5922..97434cfcf 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java @@ -76,6 +76,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { private static final String JS_RUNNER = "teavm.junit.js.runner"; private static final String THREAD_COUNT = "teavm.junit.js.threads"; private static final String SELENIUM_URL = "teavm.junit.js.selenium.url"; + private static final String JS_ENABLED = "teavm.junit.js"; private static final String C_ENABLED = "teavm.junit.c"; private static final String C_COMPILER = "teavm.junit.c-compiler"; private static final String MINIFIED = "teavm.junit.minified"; @@ -90,21 +91,30 @@ public class TeaVMTestRunner extends Runner implements Filterable { private File outputDir; private TestAdapter testAdapter = new JUnitTestAdapter(); private Map descriptions = new HashMap<>(); - private TestRunStrategy jsRunStrategy; - private static volatile TestRunner runner; + private static Map runners = new HashMap<>(); private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); - private static volatile ScheduledFuture cleanupFuture; private CountDownLatch latch; private List filteredChildren; - private CRunner cRunner; + + static class RunnerKindInfo { + volatile TestRunner runner; + volatile TestRunStrategy strategy; + volatile ScheduledFuture cleanupFuture; + } static { + for (RunKind kind : RunKind.values()) { + runners.put(kind, new RunnerKindInfo()); + runners.put(kind, new RunnerKindInfo()); + } Runtime.getRuntime().addShutdownHook(new Thread(() -> { synchronized (TeaVMTestRunner.class) { - if (runner != null) { - cleanupFuture = null; - runner.stop(); - runner.waitForCompletion(); + for (RunnerKindInfo info : runners.values()) { + if (info.runner != null) { + info.cleanupFuture = null; + info.runner.stop(); + info.runner.waitForCompletion(); + } } } })); @@ -122,6 +132,7 @@ public class TeaVMTestRunner extends Runner implements Filterable { String runStrategyName = System.getProperty(JS_RUNNER); if (runStrategyName != null) { + TestRunStrategy jsRunStrategy; switch (runStrategyName) { case "selenium": try { @@ -140,11 +151,12 @@ public class TeaVMTestRunner extends Runner implements Filterable { default: throw new InitializationError("Unknown run strategy: " + runStrategyName); } + runners.get(RunKind.JAVASCRIPT).strategy = jsRunStrategy; } String cCommand = System.getProperty(C_COMPILER); if (cCommand != null) { - cRunner = new CRunner(cCommand); + runners.get(RunKind.C).strategy = new CRunStrategy(cCommand); } } @@ -337,10 +349,6 @@ public class TeaVMTestRunner extends Runner implements Filterable { return null; } - if (jsRunStrategy == null) { - return null; - } - TestRunCallback callback = new TestRunCallback() { @Override public void complete() { @@ -360,58 +368,39 @@ public class TeaVMTestRunner extends Runner implements Filterable { private void submitRun(TestRun run) { synchronized (TeaVMTestRunner.class) { - switch (run.getKind()) { - case JAVASCRIPT: - submitJavaScriptRun(run); - break; - case C: - submitCRun(run); - break; - default: - run.getCallback().complete(); - break; + RunnerKindInfo info = runners.get(run.getKind()); + + if (info.strategy == null) { + run.getCallback().complete(); + return; } - } - } - private void submitJavaScriptRun(TestRun run) { - if (jsRunStrategy == null) { - run.getCallback().complete(); - return; - } - - if (runner == null) { - runner = new TestRunner(jsRunStrategy); - try { - runner.setNumThreads(Integer.parseInt(System.getProperty(THREAD_COUNT, "1"))); - } catch (NumberFormatException e) { - runner.setNumThreads(1); + 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(); } - runner.init(); - } - runner.run(run); + info.runner.run(run); - if (cleanupFuture != null) { - cleanupFuture.cancel(false); - cleanupFuture = null; + if (info.cleanupFuture != null) { + info.cleanupFuture.cancel(false); + info.cleanupFuture = null; + } + RunKind kind = run.getKind(); + info.cleanupFuture = executor.schedule(() -> cleanupRunner(kind), stopTimeout, TimeUnit.MILLISECONDS); } - cleanupFuture = executor.schedule(TeaVMTestRunner::cleanupRunner, stopTimeout, TimeUnit.MILLISECONDS); } - private void submitCRun(TestRun run) { - if (cRunner == null) { - run.getCallback().complete(); - return; - } - - cRunner.run(run); - } - - private static void cleanupRunner() { + private static void cleanupRunner(RunKind kind) { synchronized (TeaVMTestRunner.class) { - cleanupFuture = null; - runner.stop(); - runner = null; + RunnerKindInfo info = runners.get(kind); + info.cleanupFuture = null; + info.runner.stop(); + info.runner = null; } } @@ -496,12 +485,14 @@ public class TeaVMTestRunner extends Runner implements Filterable { private List> getJavaScriptConfigurations() { List> configurations = new ArrayList<>(); - configurations.add(TeaVMTestConfiguration.JS_DEFAULT); - if (Boolean.getBoolean(MINIFIED)) { - configurations.add(TeaVMTestConfiguration.JS_MINIFIED); - } - if (Boolean.getBoolean(OPTIMIZED)) { - configurations.add(TeaVMTestConfiguration.JS_OPTIMIZED); + if (Boolean.parseBoolean(System.getProperty(JS_ENABLED, "true"))) { + configurations.add(TeaVMTestConfiguration.JS_DEFAULT); + if (Boolean.getBoolean(MINIFIED)) { + configurations.add(TeaVMTestConfiguration.JS_MINIFIED); + } + if (Boolean.getBoolean(OPTIMIZED)) { + configurations.add(TeaVMTestConfiguration.JS_OPTIMIZED); + } } return configurations; } 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 56241ce7b..b91f0d037 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestRunStrategy.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestRunStrategy.java @@ -22,5 +22,5 @@ interface TestRunStrategy { void afterThread(); - String runTest(TestRun run) throws IOException; + 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 index 250e4b00d..3522e5f2f 100644 --- a/tools/junit/src/main/java/org/teavm/junit/TestRunner.java +++ b/tools/junit/src/main/java/org/teavm/junit/TestRunner.java @@ -15,8 +15,6 @@ */ package org.teavm.junit; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; @@ -86,25 +84,7 @@ class TestRunner { private void runImpl(TestRun run) { try { - String result = strategy.runTest(run); - if (result == null) { - run.getCallback().complete(); - return; - } - ObjectMapper mapper = new ObjectMapper(); - ObjectNode resultObject = (ObjectNode) mapper.readTree(result); - String status = resultObject.get("status").asText(); - switch (status) { - case "ok": - run.getCallback().complete(); - break; - case "exception": { - String stack = resultObject.get("stack").asText(); - String exception = resultObject.has("exception") ? resultObject.get("exception").asText() : null; - run.getCallback().error(new AssertionError(exception + "\n" + stack)); - break; - } - } + strategy.runTest(run); } catch (Exception e) { run.getCallback().error(e); }