Support async code in junit

This commit is contained in:
Alexey Andreev 2015-02-06 18:51:42 +04:00
parent 484bf61a5c
commit 1f8ef1092c
7 changed files with 111 additions and 32 deletions

View File

@ -0,0 +1,48 @@
/*
* Copyright 2015 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.classlib.java.lang;
import static org.junit.Assert.*;
import org.junit.Test;
/**
*
* @author Alexey Andreev
*/
public class ThreadTest {
@Test
public void sleeps() throws InterruptedException {
long start = System.currentTimeMillis();
Thread.sleep(100);
long duration = System.currentTimeMillis() - start;
assertTrue("Thread.sleed did not wait enogh", duration < 100);
}
@Test
public void catchesAsyncException() {
try {
throwException();
fail("Exception should have been thrown");
} catch (IllegalStateException e) {
// all is ok
}
}
private void throwException() {
Thread.yield();
throw new IllegalStateException();
}
}

View File

@ -361,7 +361,7 @@ public class TeaVMTestTool {
MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException",
Throwable.class, String.class);
vm.entryPoint("initInstance", cons);
vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName());
vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()).async();
vm.entryPoint("extractException", exceptionMsg);
vm.exportType("TestClass", cons.getClassName());
vm.setDebugEmitter(debugInfoBuilder);

View File

@ -264,7 +264,7 @@ public class TeaVMTool {
if (mainClass != null) {
MethodDescriptor mainMethodDesc = new MethodDescriptor("main", String[].class, void.class);
vm.entryPoint("main", new MethodReference(mainClass, mainMethodDesc))
.withValue(1, "java.lang.String");
.withValue(1, "java.lang.String").async();
}
for (ClassAlias alias : classAliases) {
vm.exportType(alias.getAlias(), alias.getClassName());
@ -272,7 +272,7 @@ public class TeaVMTool {
for (MethodAlias methodAlias : methodAliases) {
MethodReference ref = new MethodReference(methodAlias.getClassName(), methodAlias.getMethodName(),
MethodDescriptor.parseSignature(methodAlias.getDescriptor()));
TeaVMEntryPoint entryPoint = vm.entryPoint(methodAlias.getAlias(), ref);
TeaVMEntryPoint entryPoint = vm.entryPoint(methodAlias.getAlias(), ref).async();
if (methodAlias.getTypes() != null) {
for (int i = 0; i < methodAlias.getTypes().length; ++i) {
String types = methodAlias.getTypes()[i];
@ -299,6 +299,9 @@ public class TeaVMTool {
cancelled = true;
return;
}
if (mainClass != null) {
writer.append("main = $rt_rootInvocationAdapter(main);\n");
}
ProblemProvider problemProvider = vm.getProblemProvider();
if (problemProvider.getProblems().isEmpty()) {
log.info("JavaScript file successfully built");

View File

@ -89,6 +89,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
private TeaVMProgressListener progressListener;
private boolean cancelled;
private ListableClassHolderSource writtenClasses;
private Set<MethodReference> asyncMethods = new HashSet<>();
TeaVM(ClassReaderSource classSource, ClassLoader classLoader) {
this.classSource = classSource;
@ -435,9 +436,16 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
}
renderer.renderStringPool();
for (Map.Entry<String, TeaVMEntryPoint> entry : entryPoints.entrySet()) {
sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws()
.append("$rt_rootInvocationAdapter(")
.appendMethodBody(entry.getValue().reference).append(");").softNewLine();
sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws();
boolean wrapAsync = !asyncMethods.contains(entry.getValue().reference) && entry.getValue().isAsync();
if (wrapAsync) {
sourceWriter.append("$rt_asyncAdapter(");
}
sourceWriter.appendMethodBody(entry.getValue().reference);
if (wrapAsync) {
sourceWriter.append(")");
}
sourceWriter.append(";").softNewLine();
}
for (Map.Entry<String, String> entry : exportedClasses.entrySet()) {
sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws()
@ -529,6 +537,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
private List<ClassNode> modelToAst(ListableClassHolderSource classes) {
AsyncMethodFinder asyncFinder = new AsyncMethodFinder(dependencyChecker.getCallGraph(), diagnostics);
asyncFinder.find(classes);
asyncMethods.addAll(asyncMethods);
progressListener.phaseStarted(TeaVMPhase.DECOMPILATION, classes.getClassNames().size());
Decompiler decompiler = new Decompiler(classes, classLoader, asyncFinder.getAsyncMethods());

View File

@ -72,6 +72,7 @@ public class TeaVMEntryPoint {
private String publicName;
MethodReference reference;
private MethodDependency method;
private boolean async;
TeaVMEntryPoint(String publicName, MethodReference reference, MethodDependency method) {
this.publicName = publicName;
@ -84,6 +85,10 @@ public class TeaVMEntryPoint {
return publicName;
}
boolean isAsync() {
return async;
}
public TeaVMEntryPoint withValue(int argument, String type) {
if (argument > reference.parameterCount()) {
throw new IllegalArgumentException("Illegal argument #" + argument + " of " + reference.parameterCount());
@ -91,4 +96,9 @@ public class TeaVMEntryPoint {
method.getVariable(argument).propagate(method.getDependencyAgent().getType(type));
return this;
}
public TeaVMEntryPoint async() {
this.async = true;
return this;
}
}

View File

@ -411,12 +411,12 @@ function $rt_asyncAdapter(f) {
try {
result = f.apply(this, args);
} catch (e) {
return $rt_asyncError(e);
return $return($rt_asyncError(e));
}
return $rt_asyncResult(result);
return $return($rt_asyncResult(result));
}
}
function $rt_rootInvocationAdapter(f, extraArgs) {
function $rt_rootInvocationAdapter(f) {
return function() {
var args = Array.prototype.slice.apply(arguments);
if (extraArgs) {

View File

@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
JUnitServer = function() {
"use strict";
function JUnitServer() {
this.tree = new Tree(document.getElementById("test-tree"));
this.totalTimeSpent = 0;
this.expectedExceptions = [];
@ -113,11 +114,10 @@ JUnitServer.prototype.runTest = function(node, callback) {
this.currentTestNode = node;
var self = this;
this.loadCode(node.testCase.script, node.testCase.additionalScripts, function() {
messageHandler = function(event) {
function messageHandler(event) {
window.removeEventListener("message", messageHandler);
var timeSpent = new Date().getTime() - startTime;
node.timeIndicator.appendChild(document.createTextNode(
"(" + (timeSpent / 1000).toFixed(3) + ")"));
node.timeIndicator.appendChild(document.createTextNode("(" + (timeSpent / 1000).toFixed(3) + ")"));
self.handleEvent(JSON.parse(event.data), callback);
};
window.addEventListener("message", messageHandler);
@ -134,8 +134,7 @@ JUnitServer.prototype.runTest = function(node, callback) {
break;
}
}
node.indicator.className = "complete-indicator " +
(node.success ? "successfull" : "failed");
node.indicator.className = "complete-indicator " + (node.success ? "successfull" : "failed");
if (!node.success) {
node.open();
}
@ -253,7 +252,7 @@ JUnitServer.prototype.cleanupNode = function(node) {
}
}
Tree = function(container) {
function Tree(container) {
this.container = container;
this.nodes = [];
this.selectedNode = null;
@ -288,7 +287,7 @@ Tree.prototype.getNodes = function() {
Tree.prototype.addSelectionListener = function(listener) {
this.selectionListeners.push(listener);
}
TreeNode = function(elem, content, button, children, tree) {
function TreeNode(elem, content, button, children, tree) {
this.elem = elem;
this.content = content;
this.button = button;
@ -367,31 +366,41 @@ TreeNode.prototype.select = function() {
}
}
JUnitClient = {};
var JUnitClient = {};
JUnitClient.run = function() {
var handler = window.addEventListener("message", function() {
window.removeEventListener("message", handler);
var message = {};
try {
var instance = new TestClass();
initInstance(instance);
runTest(instance);
message.status = "ok";
runTest(instance, function(restore) {
var message = {};
try {
var result = restore();
message.status = "ok";
} catch (e) {
JUnitClient.makeErrorMessage(message, e);
}
window.parent.postMessage(JSON.stringify(message), "*");
});
} catch (e) {
message.status = "exception";
if (e.$javaException && e.$javaException.constructor.$meta) {
message.exception = e.$javaException.constructor.$meta.name;
message.stack = e.$javaException.constructor.$meta.name + ": ";
var exceptionMessage = extractException(e.$javaException);
message.stack += exceptionMessage ? $rt_ustr(exceptionMessage) : "";
message.stack += "\n" + e.stack;
} else {
message.stack = e.stack;
}
JUnitClient.makeErrorMessage(message, e);
window.parent.postMessage(JSON.stringify(message), "*");
}
window.parent.postMessage(JSON.stringify(message), "*");
});
}
JUnitClient.makeErrorMessage = function(message, e) {
message.status = "exception";
if (e.$javaException && e.$javaException.constructor.$meta) {
message.exception = e.$javaException.constructor.$meta.name;
message.stack = e.$javaException.constructor.$meta.name + ": ";
var exceptionMessage = extractException(e.$javaException);
message.stack += exceptionMessage ? $rt_ustr(exceptionMessage) : "";
message.stack += "\n" + e.stack;
} else {
message.stack = e.stack;
}
}
JUnitClient.reportError = function(error) {
var handler = window.addEventListener("message", function() {
window.removeEventListener("message", handler);