Run C test compilation in multiple threads

This commit is contained in:
Alexey Andreev 2018-04-21 12:24:31 +03:00
parent 8f0320e217
commit 38267980fb
8 changed files with 131 additions and 100 deletions

View File

@ -5,6 +5,7 @@
#include <stdlib.h>
#include <uchar.h>
#include <wchar.h>
#include <wctype.h>
#include <time.h>
#include <math.h>

View File

@ -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<String> output) throws Exception {
private boolean runCompiler(File inputFile, File outputFile, List<String> 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<String> output) throws Exception {
private boolean runProcess(Process process, List<String> 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;

View File

@ -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() {

View File

@ -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;
}
}
}
}

View File

@ -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 {

View File

@ -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<Method, Description> descriptions = new HashMap<>();
private TestRunStrategy jsRunStrategy;
private static volatile TestRunner runner;
private static Map<RunKind, RunnerKindInfo> runners = new HashMap<>();
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
private static volatile ScheduledFuture<?> cleanupFuture;
private CountDownLatch latch;
private List<Method> 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<TeaVMTestConfiguration<JavaScriptTarget>> getJavaScriptConfigurations() {
List<TeaVMTestConfiguration<JavaScriptTarget>> 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;
}

View File

@ -22,5 +22,5 @@ interface TestRunStrategy {
void afterThread();
String runTest(TestRun run) throws IOException;
void runTest(TestRun run) throws IOException;
}

View File

@ -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);
}