mirror of
https://github.com/Eaglercraft-TeaVM-Fork/eagler-teavm.git
synced 2024-12-22 16:14:10 -08:00
Add per-class compilation when running tests (requires @WholeClassCompilation
annotation)
This commit is contained in:
parent
150a613709
commit
95426e2159
|
@ -19,18 +19,20 @@
|
||||||
window.addEventListener("message", event => {
|
window.addEventListener("message", event => {
|
||||||
let request = event.data;
|
let request = event.data;
|
||||||
switch (request.type) {
|
switch (request.type) {
|
||||||
case "js":
|
case "JAVASCRIPT":
|
||||||
appendFiles(request.files, 0, () => {
|
appendFiles([request.file], 0, () => {
|
||||||
launchTest(response => {
|
launchTest(request.argument, response => {
|
||||||
event.source.postMessage(response, "*");
|
event.source.postMessage(response, "*");
|
||||||
});
|
});
|
||||||
}, error => {
|
}, error => {
|
||||||
event.source.postMessage({ status: "failed", errorMessage: error }, "*");
|
event.source.postMessage({ status: "failed", errorMessage: error }, "*");
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "wasm":
|
|
||||||
appendFiles(request.files.filter(f => f.endsWith(".js")), 0, () => {
|
case "WASM":
|
||||||
launchWasmTest(request.files.filter(f => f.endsWith(".wasm"))[0], response => {
|
const runtimeFile = request.file + "-runtime.js";
|
||||||
|
appendFiles([runtimeFile], 0, () => {
|
||||||
|
launchWasmTest(request.file, equest.argument, response => {
|
||||||
event.source.postMessage(response, "*");
|
event.source.postMessage(response, "*");
|
||||||
});
|
});
|
||||||
}, error => {
|
}, error => {
|
||||||
|
@ -57,8 +59,8 @@ function appendFiles(files, index, callback, errorCallback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function launchTest(callback) {
|
function launchTest(argument, callback) {
|
||||||
main([], result => {
|
main(argument ? [argument] : [], result => {
|
||||||
if (result instanceof Error) {
|
if (result instanceof Error) {
|
||||||
callback({
|
callback({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
|
@ -81,7 +83,7 @@ function launchTest(callback) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function launchWasmTest(path, callback) {
|
function launchWasmTest(path, argument, callback) {
|
||||||
var output = [];
|
var output = [];
|
||||||
var outputBuffer = "";
|
var outputBuffer = "";
|
||||||
|
|
||||||
|
|
|
@ -19,16 +19,6 @@ import * as fs from "./promise-fs.js";
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
import {server as WebSocketServer} from "websocket";
|
import {server as WebSocketServer} from "websocket";
|
||||||
|
|
||||||
const TEST_FILE_NAME = "test.js";
|
|
||||||
const WASM_RUNTIME_FILE_NAME = "test.wasm-runtime.js";
|
|
||||||
const TEST_FILES = [
|
|
||||||
{ file: TEST_FILE_NAME, name: "simple", type: "js" },
|
|
||||||
{ file: "test-min.js", name: "minified", type: "js" },
|
|
||||||
{ file: "test-optimized.js", name: "optimized", type: "js" },
|
|
||||||
{ file: "test.wasm", name: "wasm", type: "wasm" },
|
|
||||||
{ file: "test-optimized.wasm", name: "wasm-optimized", type: "wasm" }
|
|
||||||
];
|
|
||||||
const SERVER_PREFIX = "http://localhost:9090/";
|
|
||||||
let totalTests = 0;
|
let totalTests = 0;
|
||||||
|
|
||||||
class TestSuite {
|
class TestSuite {
|
||||||
|
@ -39,10 +29,10 @@ class TestSuite {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class TestCase {
|
class TestCase {
|
||||||
constructor(type, name, files) {
|
constructor(type, file, argument) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.name = name;
|
this.file = file;
|
||||||
this.files = files;
|
this.argument = argument
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +44,7 @@ if (rootDir.endsWith("/")) {
|
||||||
async function runAll() {
|
async function runAll() {
|
||||||
const rootSuite = new TestSuite("root");
|
const rootSuite = new TestSuite("root");
|
||||||
console.log("Searching tests");
|
console.log("Searching tests");
|
||||||
await walkDir("", "root", rootSuite);
|
await walkDir("", rootSuite);
|
||||||
|
|
||||||
console.log("Running tests");
|
console.log("Running tests");
|
||||||
|
|
||||||
|
@ -119,38 +109,29 @@ async function serveFile(path, response) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function walkDir(path, name, suite) {
|
async function walkDir(path, suite) {
|
||||||
const files = await fs.readdir(rootDir + "/" + path);
|
const files = await fs.readdir(rootDir + "/" + path);
|
||||||
if (files.includes(WASM_RUNTIME_FILE_NAME) || files.includes("test.js")) {
|
if (files.includes("tests.json")) {
|
||||||
for (const { file: fileName, name: profileName, type: type } of TEST_FILES) {
|
const descriptor = JSON.parse(await fs.readFile(`${rootDir}/${path}/tests.json`));
|
||||||
if (files.includes(fileName)) {
|
for (const { baseDir, fileName, kind, argument } of descriptor) {
|
||||||
switch (type) {
|
switch (kind) {
|
||||||
case "js":
|
case "JAVASCRIPT":
|
||||||
suite.testCases.push(new TestCase(
|
case "WASM":
|
||||||
"js", name + " " + profileName,
|
suite.testCases.push(new TestCase(kind, `${baseDir}/${fileName}`, argument));
|
||||||
[SERVER_PREFIX + path + "/" + fileName]));
|
|
||||||
break;
|
|
||||||
case "wasm":
|
|
||||||
suite.testCases.push(new TestCase(
|
|
||||||
"wasm", name + " " + profileName,
|
|
||||||
[SERVER_PREFIX + path + "/" + WASM_RUNTIME_FILE_NAME,
|
|
||||||
SERVER_PREFIX + path + "/" + fileName]));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
totalTests++;
|
totalTests++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (files) {
|
|
||||||
const childSuite = new TestSuite(name);
|
|
||||||
suite.testSuites.push(childSuite);
|
|
||||||
await Promise.all(files.map(async file => {
|
await Promise.all(files.map(async file => {
|
||||||
const filePath = path + "/" + file;
|
const filePath = path + "/" + file;
|
||||||
const stat = await fs.stat(rootDir + "/" + filePath);
|
const stat = await fs.stat(rootDir + "/" + filePath);
|
||||||
if (stat.isDirectory()) {
|
if (stat.isDirectory()) {
|
||||||
await walkDir(filePath, file, childSuite);
|
const childSuite = new TestSuite(file);
|
||||||
|
suite.testSuites.push(childSuite);
|
||||||
|
await walkDir(filePath, childSuite);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestRunner {
|
class TestRunner {
|
||||||
|
@ -182,11 +163,15 @@ class TestRunner {
|
||||||
const startTime = new Date().getTime();
|
const startTime = new Date().getTime();
|
||||||
let request = { id: this.requestIdGen++ };
|
let request = { id: this.requestIdGen++ };
|
||||||
request.tests = suite.testCases.map(testCase => {
|
request.tests = suite.testCases.map(testCase => {
|
||||||
return {
|
const result = {
|
||||||
type: testCase.type,
|
type: testCase.type,
|
||||||
name: testCase.name,
|
name: testCase.name,
|
||||||
files: testCase.files
|
file: testCase.file
|
||||||
};
|
};
|
||||||
|
if (testCase.argument) {
|
||||||
|
result.argument = testCase.argument;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
});
|
});
|
||||||
this.testsRun += suite.testCases.length;
|
this.testsRun += suite.testCases.length;
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,16 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
class CRunStrategy implements TestRunStrategy {
|
class CRunStrategy implements TestRunStrategy {
|
||||||
private String compilerCommand;
|
private String compilerCommand;
|
||||||
|
private ConcurrentMap<String, Compilation> compilationMap = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
CRunStrategy(String compilerCommand) {
|
CRunStrategy(String compilerCommand) {
|
||||||
this.compilerCommand = compilerCommand;
|
this.compilerCommand = compilerCommand;
|
||||||
|
@ -47,18 +52,21 @@ class CRunStrategy implements TestRunStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
File outputFile = new File(run.getBaseDirectory(), exeName);
|
File outputFile = new File(run.getBaseDirectory(), exeName);
|
||||||
List<String> compilerOutput = new ArrayList<>();
|
boolean compilerSuccess = compile(run.getBaseDirectory());
|
||||||
boolean compilerSuccess = runCompiler(run.getBaseDirectory(), compilerOutput);
|
|
||||||
if (!compilerSuccess) {
|
if (!compilerSuccess) {
|
||||||
run.getCallback().error(new RuntimeException("C compiler error:\n" + mergeLines(compilerOutput)));
|
run.getCallback().error(new RuntimeException("C compiler error"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
writeLines(compilerOutput);
|
|
||||||
|
|
||||||
List<String> runtimeOutput = new ArrayList<>();
|
List<String> runtimeOutput = new ArrayList<>();
|
||||||
List<String> stdout = new ArrayList<>();
|
List<String> stdout = new ArrayList<>();
|
||||||
outputFile.setExecutable(true);
|
outputFile.setExecutable(true);
|
||||||
runProcess(new ProcessBuilder(outputFile.getPath()).start(), runtimeOutput, stdout);
|
List<String> runCommand = new ArrayList<>();
|
||||||
|
runCommand.add(outputFile.getPath());
|
||||||
|
if (run.getArgument() != null) {
|
||||||
|
runCommand.add(run.getArgument());
|
||||||
|
}
|
||||||
|
runProcess(new ProcessBuilder(runCommand.toArray(new String[0])).start(), runtimeOutput, stdout);
|
||||||
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();
|
run.getCallback().complete();
|
||||||
|
@ -84,6 +92,24 @@ class CRunStrategy implements TestRunStrategy {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean compile(File inputDir) throws IOException, InterruptedException {
|
||||||
|
Compilation compilation = compilationMap.computeIfAbsent(inputDir.getPath(), k -> new Compilation());
|
||||||
|
synchronized (compilation) {
|
||||||
|
if (!compilation.started) {
|
||||||
|
compilation.started = true;
|
||||||
|
compilation.success = doCompile(inputDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return compilation.success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean doCompile(File inputDir) throws IOException, InterruptedException {
|
||||||
|
List<String> compilerOutput = new ArrayList<>();
|
||||||
|
boolean compilerSuccess = runCompiler(inputDir, compilerOutput);
|
||||||
|
writeLines(compilerOutput);
|
||||||
|
return compilerSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean runCompiler(File inputDir, List<String> output)
|
private boolean runCompiler(File inputDir, List<String> output)
|
||||||
throws IOException, InterruptedException {
|
throws IOException, InterruptedException {
|
||||||
String command = new File(compilerCommand).getAbsolutePath();
|
String command = new File(compilerCommand).getAbsolutePath();
|
||||||
|
@ -133,4 +159,9 @@ class CRunStrategy implements TestRunStrategy {
|
||||||
output.addAll(lines);
|
output.addAll(lines);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class Compilation {
|
||||||
|
volatile boolean started;
|
||||||
|
volatile boolean success;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;
|
import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;
|
||||||
|
@ -64,7 +65,6 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
HtmlPage pageRef = page.get();
|
HtmlPage pageRef = page.get();
|
||||||
|
|
||||||
pageRef.executeJavaScript(readFile(new File(run.getBaseDirectory(), run.getFileName())));
|
pageRef.executeJavaScript(readFile(new File(run.getBaseDirectory(), run.getFileName())));
|
||||||
boolean decodeStack = Boolean.parseBoolean(System.getProperty(TeaVMTestRunner.JS_DECODE_STACK, "true"));
|
boolean decodeStack = Boolean.parseBoolean(System.getProperty(TeaVMTestRunner.JS_DECODE_STACK, "true"));
|
||||||
File debugFile = decodeStack ? new File(run.getBaseDirectory(), run.getFileName() + ".teavmdbg") : null;
|
File debugFile = decodeStack ? new File(run.getBaseDirectory(), run.getFileName() + ".teavmdbg") : null;
|
||||||
|
@ -74,6 +74,7 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
|
||||||
Function function = (Function) page.get().executeJavaScript(readResource("teavm-htmlunit-adapter.js"))
|
Function function = (Function) page.get().executeJavaScript(readResource("teavm-htmlunit-adapter.js"))
|
||||||
.getJavaScriptResult();
|
.getJavaScriptResult();
|
||||||
Object[] args = new Object[] {
|
Object[] args = new Object[] {
|
||||||
|
run.getArgument(),
|
||||||
decodeStack ? createStackDecoderFunction(resultParser) : null,
|
decodeStack ? createStackDecoderFunction(resultParser) : null,
|
||||||
new NativeJavaObject(function, asyncResult, AsyncResult.class)
|
new NativeJavaObject(function, asyncResult, AsyncResult.class)
|
||||||
};
|
};
|
||||||
|
@ -161,7 +162,7 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
|
||||||
|
|
||||||
private String readFile(File file) throws IOException {
|
private String readFile(File file) throws IOException {
|
||||||
try (InputStream input = new FileInputStream(file)) {
|
try (InputStream input = new FileInputStream(file)) {
|
||||||
return IOUtils.toString(input, "UTF-8");
|
return IOUtils.toString(input, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,11 +171,11 @@ class HtmlUnitRunStrategy implements TestRunStrategy {
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return IOUtils.toString(input, "UTF-8");
|
return IOUtils.toString(input, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AsyncResult {
|
public static class AsyncResult {
|
||||||
private CountDownLatch latch = new CountDownLatch(1);
|
private CountDownLatch latch = new CountDownLatch(1);
|
||||||
private Object result;
|
private Object result;
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
package org.teavm.junit;
|
package org.teavm.junit;
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -109,6 +110,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
|
|
||||||
private static final int stopTimeout = 15000;
|
private static final int stopTimeout = 15000;
|
||||||
private Class<?> testClass;
|
private Class<?> testClass;
|
||||||
|
private boolean isWholeClassCompilation;
|
||||||
private ClassHolderSource classSource;
|
private ClassHolderSource classSource;
|
||||||
private ClassLoader classLoader;
|
private ClassLoader classLoader;
|
||||||
private Description suiteDescription;
|
private Description suiteDescription;
|
||||||
|
@ -120,6 +122,8 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
private CountDownLatch latch;
|
private CountDownLatch latch;
|
||||||
private List<Method> filteredChildren;
|
private List<Method> filteredChildren;
|
||||||
private ReferenceCache referenceCache = new ReferenceCache();
|
private ReferenceCache referenceCache = new ReferenceCache();
|
||||||
|
private boolean classCompilationOk;
|
||||||
|
private List<TestRun> runsInCurrentClass = new ArrayList<>();
|
||||||
|
|
||||||
static class RunnerKindInfo {
|
static class RunnerKindInfo {
|
||||||
volatile TestRunner runner;
|
volatile TestRunner runner;
|
||||||
|
@ -193,10 +197,17 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
latch = new CountDownLatch(children.size());
|
latch = new CountDownLatch(children.size());
|
||||||
|
|
||||||
notifier.fireTestStarted(getDescription());
|
notifier.fireTestStarted(getDescription());
|
||||||
|
isWholeClassCompilation = testClass.isAnnotationPresent(WholeClassCompilation.class);
|
||||||
|
if (isWholeClassCompilation) {
|
||||||
|
classCompilationOk = compileWholeClass(children, notifier);
|
||||||
|
}
|
||||||
for (Method child : children) {
|
for (Method child : children) {
|
||||||
runChild(child, notifier);
|
runChild(child, notifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
writeRunsDescriptor();
|
||||||
|
runsInCurrentClass.clear();
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
if (latch.await(1000, TimeUnit.MILLISECONDS)) {
|
if (latch.await(1000, TimeUnit.MILLISECONDS)) {
|
||||||
|
@ -246,6 +257,38 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
method.getName()));
|
method.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean compileWholeClass(List<Method> children, RunNotifier notifier) {
|
||||||
|
File outputPath = getOutputPathForClass();
|
||||||
|
boolean hasErrors = false;
|
||||||
|
Description description = getDescription();
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<JavaScriptTarget> configuration : getJavaScriptConfigurations()) {
|
||||||
|
CompileResult result = compileToJs(wholeClass(children), "classTest", configuration, outputPath);
|
||||||
|
if (!result.success) {
|
||||||
|
hasErrors = true;
|
||||||
|
notifier.fireTestFailure(createFailure(description, result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<CTarget> configuration : getCConfigurations()) {
|
||||||
|
CompileResult result = compileToC(wholeClass(children), "classTest", configuration, outputPath);
|
||||||
|
if (!result.success) {
|
||||||
|
hasErrors = true;
|
||||||
|
notifier.fireTestFailure(createFailure(description, result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) {
|
||||||
|
CompileResult result = compileToWasm(wholeClass(children), "classTest", configuration, outputPath);
|
||||||
|
if (!result.success) {
|
||||||
|
hasErrors = true;
|
||||||
|
notifier.fireTestFailure(createFailure(description, result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return !hasErrors;
|
||||||
|
}
|
||||||
|
|
||||||
private void runChild(Method child, RunNotifier notifier) {
|
private void runChild(Method child, RunNotifier notifier) {
|
||||||
Description description = describeChild(child);
|
Description description = describeChild(child);
|
||||||
notifier.fireTestStarted(description);
|
notifier.fireTestStarted(description);
|
||||||
|
@ -273,62 +316,37 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!child.isAnnotationPresent(SkipJVM.class)
|
if (!child.isAnnotationPresent(SkipJVM.class) && !testClass.isAnnotationPresent(SkipJVM.class)) {
|
||||||
&& !testClass.isAnnotationPresent(SkipJVM.class)) {
|
|
||||||
ran = true;
|
ran = true;
|
||||||
success = runInJvm(child, notifier, expectedExceptions);
|
success = runInJvm(child, notifier, expectedExceptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success && outputDir != null) {
|
if (success && outputDir != null) {
|
||||||
int[] configurationIndex = new int[] { 0 };
|
int[] configurationIndex = new int[] { 0 };
|
||||||
List<Consumer<Boolean>> onSuccess = new ArrayList<>();
|
|
||||||
|
|
||||||
List<TestRun> runs = new ArrayList<>();
|
List<TestRun> runs = new ArrayList<>();
|
||||||
onSuccess.add(runSuccess -> {
|
Consumer<Boolean> onSuccess = runSuccess -> {
|
||||||
if (runSuccess && configurationIndex[0] < runs.size()) {
|
if (runSuccess && configurationIndex[0] < runs.size()) {
|
||||||
submitRun(runs.get(configurationIndex[0]++));
|
submitRun(runs.get(configurationIndex[0]++));
|
||||||
} else {
|
} else {
|
||||||
notifier.fireTestFinished(description);
|
notifier.fireTestFinished(description);
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
try {
|
if (isWholeClassCompilation) {
|
||||||
File outputPath = getOutputPath(child);
|
if (!classCompilationOk) {
|
||||||
copyJsFilesTo(outputPath);
|
|
||||||
|
|
||||||
for (TeaVMTestConfiguration<JavaScriptTarget> configuration : getJavaScriptConfigurations()) {
|
|
||||||
TestRun run = compile(child, notifier, RunKind.JAVASCRIPT,
|
|
||||||
m -> compileToJs(m, configuration, outputPath), onSuccess.get(0));
|
|
||||||
if (run != null) {
|
|
||||||
runs.add(run);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TeaVMTestConfiguration<CTarget> configuration : getCConfigurations()) {
|
|
||||||
TestRun run = compile(child, notifier, RunKind.C,
|
|
||||||
m -> compileToC(m, configuration, outputPath), onSuccess.get(0));
|
|
||||||
if (run != null) {
|
|
||||||
runs.add(run);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) {
|
|
||||||
TestRun run = compile(child, notifier, RunKind.WASM,
|
|
||||||
m -> compileToWasm(m, configuration, outputPath), onSuccess.get(0));
|
|
||||||
if (run != null) {
|
|
||||||
runs.add(run);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Throwable e) {
|
|
||||||
notifier.fireTestFailure(new Failure(description, e));
|
|
||||||
notifier.fireTestFinished(description);
|
notifier.fireTestFinished(description);
|
||||||
|
notifier.fireTestFailure(new Failure(description,
|
||||||
|
new AssertionError("Could not compile test class")));
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
return;
|
} else {
|
||||||
|
runTestsFromWholeClass(child, notifier, runs, onSuccess);
|
||||||
|
onSuccess.accept(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
runCompiledTest(child, notifier, runs, onSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuccess.get(0).accept(true);
|
|
||||||
} else {
|
} else {
|
||||||
if (!ran) {
|
if (!ran) {
|
||||||
notifier.fireTestIgnored(description);
|
notifier.fireTestIgnored(description);
|
||||||
|
@ -338,6 +356,81 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runTestsFromWholeClass(Method child, RunNotifier notifier, List<TestRun> runs,
|
||||||
|
Consumer<Boolean> onSuccess) {
|
||||||
|
File outputPath = getOutputPathForClass();
|
||||||
|
MethodDescriptor descriptor = getDescriptor(child);
|
||||||
|
MethodReference reference = new MethodReference(testClass.getName(), descriptor);
|
||||||
|
|
||||||
|
File testFilePath = getOutputPath(child);
|
||||||
|
testFilePath.mkdirs();
|
||||||
|
|
||||||
|
boolean hasJsOrWasm = false;
|
||||||
|
for (TeaVMTestConfiguration<JavaScriptTarget> configuration : getJavaScriptConfigurations()) {
|
||||||
|
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".js");
|
||||||
|
runs.add(createTestRun(testPath, child, RunKind.JAVASCRIPT, reference.toString(), notifier, onSuccess));
|
||||||
|
hasJsOrWasm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) {
|
||||||
|
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm");
|
||||||
|
runs.add(createTestRun(testPath, child, RunKind.WASM, reference.toString(), notifier, onSuccess));
|
||||||
|
hasJsOrWasm = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<CTarget> configuration : getCConfigurations()) {
|
||||||
|
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), true, ".c");
|
||||||
|
runs.add(createTestRun(testPath, child, RunKind.C, reference.toString(), notifier, onSuccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasJsOrWasm) {
|
||||||
|
try {
|
||||||
|
copyJsFilesTo(testFilePath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runCompiledTest(Method child, RunNotifier notifier, List<TestRun> runs, Consumer<Boolean> onSuccess) {
|
||||||
|
try {
|
||||||
|
File outputPath = getOutputPath(child);
|
||||||
|
copyJsFilesTo(outputPath);
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<JavaScriptTarget> configuration : getJavaScriptConfigurations()) {
|
||||||
|
CompileResult compileResult = compileToJs(singleTest(child), "test", configuration, outputPath);
|
||||||
|
TestRun run = prepareRun(child, compileResult, notifier, RunKind.JAVASCRIPT, onSuccess);
|
||||||
|
if (run != null) {
|
||||||
|
runs.add(run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<CTarget> configuration : getCConfigurations()) {
|
||||||
|
CompileResult compileResult = compileToC(singleTest(child), "test", configuration, outputPath);
|
||||||
|
TestRun run = prepareRun(child, compileResult, notifier, RunKind.C, onSuccess);
|
||||||
|
if (run != null) {
|
||||||
|
runs.add(run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (TeaVMTestConfiguration<WasmTarget> configuration : getWasmConfigurations()) {
|
||||||
|
CompileResult compileResult = compileToWasm(singleTest(child), "test", configuration,
|
||||||
|
outputPath);
|
||||||
|
TestRun run = prepareRun(child, compileResult, notifier, RunKind.WASM, onSuccess);
|
||||||
|
if (run != null) {
|
||||||
|
runs.add(run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
notifier.fireTestFailure(new Failure(describeChild(child), e));
|
||||||
|
notifier.fireTestFinished(describeChild(child));
|
||||||
|
latch.countDown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess.accept(true);
|
||||||
|
}
|
||||||
|
|
||||||
private String[] getExpectedExceptions(MethodHolder method) {
|
private String[] getExpectedExceptions(MethodHolder method) {
|
||||||
AnnotationHolder annot = method.getAnnotations().get(JUNIT4_TEST);
|
AnnotationHolder annot = method.getAnnotations().get(JUNIT4_TEST);
|
||||||
if (annot == null) {
|
if (annot == null) {
|
||||||
|
@ -438,7 +531,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
void run() throws Throwable;
|
void run() throws Throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
class JUnit4Runner implements Runner {
|
static class JUnit4Runner implements Runner {
|
||||||
Object instance;
|
Object instance;
|
||||||
Method child;
|
Method child;
|
||||||
|
|
||||||
|
@ -457,7 +550,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JUnit3Runner implements Runner {
|
static class JUnit3Runner implements Runner {
|
||||||
Object instance;
|
Object instance;
|
||||||
|
|
||||||
JUnit3Runner(Object instance) {
|
JUnit3Runner(Object instance) {
|
||||||
|
@ -470,25 +563,24 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private TestRun compile(Method child, RunNotifier notifier, RunKind kind,
|
private TestRun prepareRun(Method child, CompileResult result, RunNotifier notifier, RunKind kind,
|
||||||
CompileFunction compiler, Consumer<Boolean> onComplete) {
|
Consumer<Boolean> onComplete) {
|
||||||
Description description = describeChild(child);
|
Description description = describeChild(child);
|
||||||
|
|
||||||
CompileResult compileResult;
|
if (!result.success) {
|
||||||
try {
|
notifier.fireTestFailure(createFailure(description, result));
|
||||||
compileResult = compiler.compile(child);
|
|
||||||
} catch (Exception e) {
|
|
||||||
notifier.fireTestFailure(new Failure(description, e));
|
|
||||||
notifier.fireTestFinished(description);
|
notifier.fireTestFinished(description);
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!compileResult.success) {
|
return createTestRun(result.file, child, kind, null, notifier, onComplete);
|
||||||
notifier.fireTestFailure(new Failure(description, new AssertionError(compileResult.errorMessage)));
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private TestRun createTestRun(File file, Method child, RunKind kind, String argument, RunNotifier notifier,
|
||||||
|
Consumer<Boolean> onComplete) {
|
||||||
|
Description description = describeChild(child);
|
||||||
|
|
||||||
TestRunCallback callback = new TestRunCallback() {
|
TestRunCallback callback = new TestRunCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void complete() {
|
public void complete() {
|
||||||
|
@ -502,12 +594,21 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return new TestRun(compileResult.file.getParentFile(), child, description, compileResult.file.getName(),
|
return new TestRun(file.getParentFile(), child, description, file.getName(), kind,
|
||||||
kind, callback);
|
argument, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Failure createFailure(Description description, CompileResult result) {
|
||||||
|
Throwable throwable = result.throwable;
|
||||||
|
if (throwable == null) {
|
||||||
|
throwable = new AssertionError(result.errorMessage);
|
||||||
|
}
|
||||||
|
return new Failure(description, throwable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submitRun(TestRun run) {
|
private void submitRun(TestRun run) {
|
||||||
synchronized (TeaVMTestRunner.class) {
|
synchronized (TeaVMTestRunner.class) {
|
||||||
|
runsInCurrentClass.add(run);
|
||||||
RunnerKindInfo info = runners.get(run.getKind());
|
RunnerKindInfo info = runners.get(run.getKind());
|
||||||
|
|
||||||
if (info.strategy == null) {
|
if (info.strategy == null) {
|
||||||
|
@ -552,14 +653,20 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private File getOutputPathForClass() {
|
||||||
|
File path = outputDir;
|
||||||
|
path = new File(path, testClass.getName().replace('.', '/'));
|
||||||
|
path.mkdirs();
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
private void copyJsFilesTo(File path) throws IOException {
|
private void copyJsFilesTo(File path) throws IOException {
|
||||||
resourceToFile("org/teavm/backend/wasm/wasm-runtime.js", new File(path, "test.wasm-runtime.js"));
|
|
||||||
resourceToFile("teavm-run-test.html", new File(path, "run-test.html"));
|
resourceToFile("teavm-run-test.html", new File(path, "run-test.html"));
|
||||||
resourceToFile("teavm-run-test-wasm.html", new File(path, "run-test-wasm.html"));
|
resourceToFile("teavm-run-test-wasm.html", new File(path, "run-test-wasm.html"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompileResult compileToJs(Method method, TeaVMTestConfiguration<JavaScriptTarget> configuration,
|
private CompileResult compileToJs(Consumer<TeaVM> additionalProcessing, String baseName,
|
||||||
File path) {
|
TeaVMTestConfiguration<JavaScriptTarget> configuration, File path) {
|
||||||
boolean decodeStack = Boolean.parseBoolean(System.getProperty(JS_DECODE_STACK, "true"));
|
boolean decodeStack = Boolean.parseBoolean(System.getProperty(JS_DECODE_STACK, "true"));
|
||||||
DebugInformationBuilder debugEmitter = new DebugInformationBuilder(new ReferenceCache());
|
DebugInformationBuilder debugEmitter = new DebugInformationBuilder(new ReferenceCache());
|
||||||
Supplier<JavaScriptTarget> targetSupplier = () -> {
|
Supplier<JavaScriptTarget> targetSupplier = () -> {
|
||||||
|
@ -595,12 +702,12 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return compileTest(method, configuration, targetSupplier, TestEntryPoint.class.getName(), path, ".js",
|
return compile(configuration, targetSupplier, TestEntryPoint.class.getName(), path, ".js",
|
||||||
postBuild, false);
|
postBuild, false, additionalProcessing, baseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompileResult compileToC(Method method, TeaVMTestConfiguration<CTarget> configuration,
|
private CompileResult compileToC(Consumer<TeaVM> additionalProcessing, String baseName,
|
||||||
File path) {
|
TeaVMTestConfiguration<CTarget> configuration, File path) {
|
||||||
CompilePostProcessor postBuild = (vm, file) -> {
|
CompilePostProcessor postBuild = (vm, file) -> {
|
||||||
try {
|
try {
|
||||||
resourceToFile("teavm-CMakeLists.txt", new File(file.getParent(), "CMakeLists.txt"));
|
resourceToFile("teavm-CMakeLists.txt", new File(file.getParent(), "CMakeLists.txt"));
|
||||||
|
@ -608,8 +715,8 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return compileTest(method, configuration, this::createCTarget, TestNativeEntryPoint.class.getName(), path, ".c",
|
return compile(configuration, this::createCTarget, TestNativeEntryPoint.class.getName(), path, ".c",
|
||||||
postBuild, true);
|
postBuild, true, additionalProcessing, baseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CTarget createCTarget() {
|
private CTarget createCTarget() {
|
||||||
|
@ -618,39 +725,50 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
return cTarget;
|
return cTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CompileResult compileToWasm(Method method, TeaVMTestConfiguration<WasmTarget> configuration,
|
private CompileResult compileToWasm(Consumer<TeaVM> additionalProcessing, String baseName,
|
||||||
File path) {
|
TeaVMTestConfiguration<WasmTarget> configuration, File path) {
|
||||||
return compileTest(method, configuration, WasmTarget::new, TestNativeEntryPoint.class.getName(), path,
|
return compile(configuration, WasmTarget::new, TestNativeEntryPoint.class.getName(), path,
|
||||||
".wasm", null, false);
|
".wasm", null, false, additionalProcessing, baseName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends TeaVMTarget> CompileResult compileTest(Method method, TeaVMTestConfiguration<T> configuration,
|
private Consumer<TeaVM> singleTest(Method method) {
|
||||||
|
ClassHolder classHolder = classSource.get(method.getDeclaringClass().getName());
|
||||||
|
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
|
||||||
|
|
||||||
|
return vm -> {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
applyProperties(method.getDeclaringClass(), properties);
|
||||||
|
vm.setProperties(properties);
|
||||||
|
new TestEntryPointTransformerForSingleMethod(methodHolder.getReference(), testClass.getName()).install(vm);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Consumer<TeaVM> wholeClass(List<Method> methods) {
|
||||||
|
return vm -> {
|
||||||
|
Properties properties = new Properties();
|
||||||
|
applyProperties(testClass, properties);
|
||||||
|
vm.setProperties(properties);
|
||||||
|
List<MethodReference> methodReferences = new ArrayList<>();
|
||||||
|
for (Method method : methods) {
|
||||||
|
ClassHolder classHolder = classSource.get(method.getDeclaringClass().getName());
|
||||||
|
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
|
||||||
|
methodReferences.add(methodHolder.getReference());
|
||||||
|
}
|
||||||
|
new TestEntryPointTransformerForWholeClass(methodReferences, testClass.getName()).install(vm);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T extends TeaVMTarget> CompileResult compile(TeaVMTestConfiguration<T> configuration,
|
||||||
Supplier<T> targetSupplier, String entryPoint, File path, String extension,
|
Supplier<T> targetSupplier, String entryPoint, File path, String extension,
|
||||||
CompilePostProcessor postBuild, boolean separateDir) {
|
CompilePostProcessor postBuild, boolean separateDir,
|
||||||
|
Consumer<TeaVM> additionalProcessing, String baseName) {
|
||||||
CompileResult result = new CompileResult();
|
CompileResult result = new CompileResult();
|
||||||
|
|
||||||
StringBuilder simpleName = new StringBuilder();
|
File outputFile = getOutputFile(path, baseName, configuration.getSuffix(), separateDir, extension);
|
||||||
simpleName.append("test");
|
|
||||||
String suffix = configuration.getSuffix();
|
|
||||||
if (!suffix.isEmpty()) {
|
|
||||||
if (!separateDir) {
|
|
||||||
simpleName.append('-').append(suffix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File outputFile;
|
|
||||||
if (separateDir) {
|
|
||||||
outputFile = new File(new File(path, simpleName.toString()), "test" + extension);
|
|
||||||
} else {
|
|
||||||
simpleName.append(extension);
|
|
||||||
outputFile = new File(path, simpleName.toString());
|
|
||||||
}
|
|
||||||
result.file = outputFile;
|
result.file = outputFile;
|
||||||
|
|
||||||
ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader();
|
ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader();
|
||||||
|
|
||||||
ClassHolder classHolder = classSource.get(method.getDeclaringClass().getName());
|
|
||||||
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
|
|
||||||
|
|
||||||
T target = targetSupplier.get();
|
T target = targetSupplier.get();
|
||||||
configuration.apply(target);
|
configuration.apply(target);
|
||||||
|
|
||||||
|
@ -660,6 +778,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
dependencyAnalyzerFactory = FastDependencyAnalyzer::new;
|
dependencyAnalyzerFactory = FastDependencyAnalyzer::new;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
TeaVM vm = new TeaVMBuilder(target)
|
TeaVM vm = new TeaVMBuilder(target)
|
||||||
.setClassLoader(classLoader)
|
.setClassLoader(classLoader)
|
||||||
.setClassSource(classSource)
|
.setClassSource(classSource)
|
||||||
|
@ -667,15 +786,11 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
.setDependencyAnalyzerFactory(dependencyAnalyzerFactory)
|
.setDependencyAnalyzerFactory(dependencyAnalyzerFactory)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Properties properties = new Properties();
|
|
||||||
applyProperties(method.getDeclaringClass(), properties);
|
|
||||||
vm.setProperties(properties);
|
|
||||||
|
|
||||||
configuration.apply(vm);
|
configuration.apply(vm);
|
||||||
|
additionalProcessing.accept(vm);
|
||||||
vm.installPlugins();
|
vm.installPlugins();
|
||||||
|
|
||||||
new TestExceptionPlugin().install(vm);
|
new TestExceptionPlugin().install(vm);
|
||||||
new TestEntryPointTransformer(methodHolder.getReference(), testClass.getName()).install(vm);
|
|
||||||
|
|
||||||
vm.entryPoint(entryPoint);
|
vm.entryPoint(entryPoint);
|
||||||
|
|
||||||
|
@ -697,6 +812,31 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
} catch (Exception e) {
|
||||||
|
result = new CompileResult();
|
||||||
|
result.success = false;
|
||||||
|
result.throwable = e;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getOutputFile(File path, String baseName, String suffix, boolean separateDir, String extension) {
|
||||||
|
StringBuilder simpleName = new StringBuilder();
|
||||||
|
simpleName.append(baseName);
|
||||||
|
if (!suffix.isEmpty()) {
|
||||||
|
if (!separateDir) {
|
||||||
|
simpleName.append('-').append(suffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File outputFile;
|
||||||
|
if (separateDir) {
|
||||||
|
outputFile = new File(new File(path, simpleName.toString()), "test" + extension);
|
||||||
|
} else {
|
||||||
|
simpleName.append(extension);
|
||||||
|
outputFile = new File(path, simpleName.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CompilePostProcessor {
|
interface CompilePostProcessor {
|
||||||
|
@ -796,13 +936,92 @@ public class TeaVMTestRunner extends Runner implements Filterable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void writeRunsDescriptor() {
|
||||||
|
if (runsInCurrentClass.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
File outputDir = getOutputPathForClass();
|
||||||
|
outputDir.mkdirs();
|
||||||
|
File descriptorFile = new File(outputDir, "tests.json");
|
||||||
|
try (OutputStream output = new FileOutputStream(descriptorFile);
|
||||||
|
OutputStream bufferedOutput = new BufferedOutputStream(output);
|
||||||
|
Writer writer = new OutputStreamWriter(bufferedOutput)) {
|
||||||
|
writer.write("[\n");
|
||||||
|
boolean first = true;
|
||||||
|
for (TestRun run : runsInCurrentClass) {
|
||||||
|
if (!first) {
|
||||||
|
writer.write(",\n");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
writer.write(" {\n");
|
||||||
|
writer.write(" \"baseDir\": ");
|
||||||
|
writeJsonString(writer, run.getBaseDirectory().getAbsolutePath().replace('\\', '/'));
|
||||||
|
writer.write(",\n");
|
||||||
|
writer.write(" \"fileName\": ");
|
||||||
|
writeJsonString(writer, run.getFileName());
|
||||||
|
writer.write(",\n");
|
||||||
|
writer.write(" \"kind\": \"" + run.getKind().name() + "\"");
|
||||||
|
if (run.getArgument() != null) {
|
||||||
|
writer.write(",\n");
|
||||||
|
writer.write(" \"argument\": ");
|
||||||
|
writeJsonString(writer, run.getArgument());
|
||||||
|
}
|
||||||
|
writer.write("\n }");
|
||||||
|
}
|
||||||
|
writer.write("\n]");
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeJsonString(Writer writer, String s) throws IOException {
|
||||||
|
writer.write('"');
|
||||||
|
for (int i = 0; i < s.length(); ++i) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '"':
|
||||||
|
writer.write("\\\"");
|
||||||
|
case '\\':
|
||||||
|
writer.write("\\\\");
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
writer.write("\\r");
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
writer.write("\\n");
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
writer.write("\\t");
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
writer.write("\\f");
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
writer.write("\\b");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (c < ' ') {
|
||||||
|
writer.write("\\u00");
|
||||||
|
writer.write(hex(c / 16));
|
||||||
|
writer.write(hex(c % 16));
|
||||||
|
} else {
|
||||||
|
writer.write(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.write('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char hex(int digit) {
|
||||||
|
return (char) (digit < 10 ? '0' + digit : 'A' + digit - 10);
|
||||||
|
}
|
||||||
|
|
||||||
static class CompileResult {
|
static class CompileResult {
|
||||||
boolean success = true;
|
boolean success = true;
|
||||||
String errorMessage;
|
String errorMessage;
|
||||||
File file;
|
File file;
|
||||||
}
|
Throwable throwable;
|
||||||
|
|
||||||
interface CompileFunction {
|
|
||||||
CompileResult compile(Method method);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,10 +21,10 @@ final class TestEntryPoint {
|
||||||
private TestEntryPoint() {
|
private TestEntryPoint() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void run() throws Exception {
|
public static void run(String name) throws Exception {
|
||||||
before();
|
before();
|
||||||
try {
|
try {
|
||||||
launchTest();
|
launchTest(name);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
after();
|
after();
|
||||||
|
@ -36,11 +36,11 @@ final class TestEntryPoint {
|
||||||
|
|
||||||
private static native void before();
|
private static native void before();
|
||||||
|
|
||||||
private static native void launchTest() throws Exception;
|
private static native void launchTest(String name) throws Exception;
|
||||||
|
|
||||||
private static native void after();
|
private static native void after();
|
||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
public static void main(String[] args) throws Throwable {
|
||||||
run();
|
run(args.length == 1 ? args[0] : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,12 +45,10 @@ import org.teavm.model.emit.ValueEmitter;
|
||||||
import org.teavm.vm.spi.TeaVMHost;
|
import org.teavm.vm.spi.TeaVMHost;
|
||||||
import org.teavm.vm.spi.TeaVMPlugin;
|
import org.teavm.vm.spi.TeaVMPlugin;
|
||||||
|
|
||||||
class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
|
abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
|
||||||
private MethodReference testMethod;
|
|
||||||
private String testClassName;
|
private String testClassName;
|
||||||
|
|
||||||
TestEntryPointTransformer(MethodReference testMethod, String testClassName) {
|
TestEntryPointTransformer(String testClassName) {
|
||||||
this.testMethod = testMethod;
|
|
||||||
this.testClassName = testClassName;
|
this.testClassName = testClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,11 +89,11 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
|
||||||
});
|
});
|
||||||
ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
|
ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
|
||||||
|
|
||||||
if (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName(), false)) {
|
if (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testClassName, false)) {
|
||||||
testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_BEFORE);
|
testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_BEFORE);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ClassReader> classes = collectSuperClasses(pe.getClassSource(), testMethod.getClassName());
|
List<ClassReader> classes = collectSuperClasses(pe.getClassSource(), testClassName);
|
||||||
Collections.reverse(classes);
|
Collections.reverse(classes);
|
||||||
classes.stream()
|
classes.stream()
|
||||||
.flatMap(cls -> cls.getMethods().stream())
|
.flatMap(cls -> cls.getMethods().stream())
|
||||||
|
@ -110,13 +108,13 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
|
||||||
ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
|
ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
|
||||||
ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
|
ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
|
||||||
|
|
||||||
List<ClassReader> classes = collectSuperClasses(pe.getClassSource(), testMethod.getClassName());
|
List<ClassReader> classes = collectSuperClasses(pe.getClassSource(), testClassName);
|
||||||
classes.stream()
|
classes.stream()
|
||||||
.flatMap(cls -> cls.getMethods().stream())
|
.flatMap(cls -> cls.getMethods().stream())
|
||||||
.filter(m -> m.getAnnotations().get(JUNIT4_AFTER) != null)
|
.filter(m -> m.getAnnotations().get(JUNIT4_AFTER) != null)
|
||||||
.forEach(m -> testCaseVar.cast(ValueType.object(m.getOwnerName())).invokeVirtual(m.getReference()));
|
.forEach(m -> testCaseVar.cast(ValueType.object(m.getOwnerName())).invokeVirtual(m.getReference()));
|
||||||
|
|
||||||
if (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName(), false)) {
|
if (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testClassName, false)) {
|
||||||
testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_AFTER);
|
testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_AFTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,8 +135,10 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy) {
|
protected abstract Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy);
|
||||||
ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
|
|
||||||
|
protected final void generateSingleMethodLaunchProgram(MethodReference testMethod,
|
||||||
|
ClassHierarchy hierarchy, ProgramEmitter pe) {
|
||||||
pe.getField(TestEntryPoint.class, "testCase", Object.class)
|
pe.getField(TestEntryPoint.class, "testCase", Object.class)
|
||||||
.cast(ValueType.object(testMethod.getClassName()))
|
.cast(ValueType.object(testMethod.getClassName()))
|
||||||
.invokeSpecial(testMethod);
|
.invokeSpecial(testMethod);
|
||||||
|
@ -163,6 +163,5 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
|
||||||
} else {
|
} else {
|
||||||
pe.exit();
|
pe.exit();
|
||||||
}
|
}
|
||||||
return pe.getProgram();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 org.teavm.model.ClassHierarchy;
|
||||||
|
import org.teavm.model.MethodHolder;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
import org.teavm.model.Program;
|
||||||
|
import org.teavm.model.emit.ProgramEmitter;
|
||||||
|
|
||||||
|
class TestEntryPointTransformerForSingleMethod extends TestEntryPointTransformer {
|
||||||
|
private MethodReference testMethod;
|
||||||
|
|
||||||
|
TestEntryPointTransformerForSingleMethod(MethodReference testMethod, String testClassName) {
|
||||||
|
super(testClassName);
|
||||||
|
this.testMethod = testMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy) {
|
||||||
|
ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
|
||||||
|
generateSingleMethodLaunchProgram(testMethod, hierarchy, pe);
|
||||||
|
return pe.getProgram();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* 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.List;
|
||||||
|
import org.teavm.model.ClassHierarchy;
|
||||||
|
import org.teavm.model.MethodHolder;
|
||||||
|
import org.teavm.model.MethodReference;
|
||||||
|
import org.teavm.model.Program;
|
||||||
|
import org.teavm.model.emit.ForkEmitter;
|
||||||
|
import org.teavm.model.emit.ProgramEmitter;
|
||||||
|
import org.teavm.model.emit.ValueEmitter;
|
||||||
|
import org.teavm.model.instructions.BranchingCondition;
|
||||||
|
|
||||||
|
class TestEntryPointTransformerForWholeClass extends TestEntryPointTransformer {
|
||||||
|
private List<MethodReference> testMethods;
|
||||||
|
|
||||||
|
TestEntryPointTransformerForWholeClass(List<MethodReference> testMethods, String testClassName) {
|
||||||
|
super(testClassName);
|
||||||
|
this.testMethods = testMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy) {
|
||||||
|
ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
|
||||||
|
ValueEmitter testName = pe.var(1, String.class);
|
||||||
|
|
||||||
|
for (MethodReference testMethod : testMethods) {
|
||||||
|
ValueEmitter isTest = testName.invokeSpecial("equals", boolean.class,
|
||||||
|
pe.constant(testMethod.toString()).cast(Object.class));
|
||||||
|
ForkEmitter fork = isTest.fork(BranchingCondition.NOT_EQUAL);
|
||||||
|
pe.enter(pe.getProgram().createBasicBlock());
|
||||||
|
fork.setThen(pe.getBlock());
|
||||||
|
|
||||||
|
generateSingleMethodLaunchProgram(testMethod, hierarchy, pe);
|
||||||
|
pe.enter(pe.getProgram().createBasicBlock());
|
||||||
|
fork.setElse(pe.getBlock());
|
||||||
|
}
|
||||||
|
|
||||||
|
pe.construct(IllegalArgumentException.class, pe.constant("Invalid test name")).raise();
|
||||||
|
|
||||||
|
return pe.getProgram();
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ final class TestNativeEntryPoint {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
try {
|
try {
|
||||||
TestEntryPoint.run();
|
TestEntryPoint.run(args.length > 0 ? args[0] : null);
|
||||||
new PrintStream(StdoutOutputStream.INSTANCE).println("SUCCESS");
|
new PrintStream(StdoutOutputStream.INSTANCE).println("SUCCESS");
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
PrintStream out = new PrintStream(StderrOutputStream.INSTANCE);
|
PrintStream out = new PrintStream(StderrOutputStream.INSTANCE);
|
||||||
|
|
|
@ -26,14 +26,16 @@ class TestRun {
|
||||||
private String fileName;
|
private String fileName;
|
||||||
private RunKind kind;
|
private RunKind kind;
|
||||||
private TestRunCallback callback;
|
private TestRunCallback callback;
|
||||||
|
private String argument;
|
||||||
|
|
||||||
TestRun(File baseDirectory, Method method, Description description, String fileName, RunKind kind,
|
TestRun(File baseDirectory, Method method, Description description, String fileName, RunKind kind,
|
||||||
TestRunCallback callback) {
|
String argument, TestRunCallback callback) {
|
||||||
this.baseDirectory = baseDirectory;
|
this.baseDirectory = baseDirectory;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
|
this.argument = argument;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +59,10 @@ class TestRun {
|
||||||
return kind;
|
return kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getArgument() {
|
||||||
|
return argument;
|
||||||
|
}
|
||||||
|
|
||||||
public TestRunCallback getCallback() {
|
public TestRunCallback getCallback() {
|
||||||
return callback;
|
return callback;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2020 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.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface WholeClassCompilation {
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
var $rt_decodeStack;
|
var $rt_decodeStack;
|
||||||
|
|
||||||
function runMain(stackDecoder, callback) {
|
function runMain(argument, stackDecoder, callback) {
|
||||||
$rt_decodeStack = stackDecoder;
|
$rt_decodeStack = stackDecoder;
|
||||||
main([], function(result) {
|
main(argument !== null ? [argument] : [], function(result) {
|
||||||
var message = {};
|
var message = {};
|
||||||
if (result instanceof Error) {
|
if (result instanceof Error) {
|
||||||
makeErrorMessage(message, result);
|
makeErrorMessage(message, result);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<script type="text/javascript" src="classTest.js"></script>
|
||||||
<script type="text/javascript" src="test.js"></script>
|
<script type="text/javascript" src="test.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
main([], function(result) {
|
main([], function(result) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user