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", MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException",
Throwable.class, String.class); Throwable.class, String.class);
vm.entryPoint("initInstance", cons); 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.entryPoint("extractException", exceptionMsg);
vm.exportType("TestClass", cons.getClassName()); vm.exportType("TestClass", cons.getClassName());
vm.setDebugEmitter(debugInfoBuilder); vm.setDebugEmitter(debugInfoBuilder);

View File

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

View File

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

View File

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

View File

@ -411,12 +411,12 @@ function $rt_asyncAdapter(f) {
try { try {
result = f.apply(this, args); result = f.apply(this, args);
} catch (e) { } 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() { return function() {
var args = Array.prototype.slice.apply(arguments); var args = Array.prototype.slice.apply(arguments);
if (extraArgs) { if (extraArgs) {

View File

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