From 1f8ef1092ca53514f9d439f509e17e88eb1d3024 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 6 Feb 2015 18:51:42 +0400 Subject: [PATCH] Support async code in junit --- .../teavm/classlib/java/lang/ThreadTest.java | 48 ++++++++++++++++ .../java/org/teavm/tooling/TeaVMTestTool.java | 2 +- .../java/org/teavm/tooling/TeaVMTool.java | 7 ++- .../src/main/java/org/teavm/vm/TeaVM.java | 15 ++++- .../java/org/teavm/vm/TeaVMEntryPoint.java | 10 ++++ .../resources/org/teavm/javascript/runtime.js | 6 +- .../teavm/tooling/test/res/junit-support.js | 55 +++++++++++-------- 7 files changed, 111 insertions(+), 32 deletions(-) create mode 100644 teavm-classlib/src/test/java/org/teavm/classlib/java/lang/ThreadTest.java diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/ThreadTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/ThreadTest.java new file mode 100644 index 000000000..22f7230bd --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/ThreadTest.java @@ -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(); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java index cc7867722..1bf954c91 100644 --- a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java @@ -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); diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java index 7e9b1cd9b..2e432347b 100644 --- a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -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"); diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java index 7a03aa30b..6f974e1e1 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -89,6 +89,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { private TeaVMProgressListener progressListener; private boolean cancelled; private ListableClassHolderSource writtenClasses; + private Set 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 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 entry : exportedClasses.entrySet()) { sourceWriter.append("var ").append(entry.getKey()).ws().append("=").ws() @@ -529,6 +537,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { private List 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()); diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java b/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java index ca6b948d1..bcd0cf12e 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java @@ -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; + } } diff --git a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js index 8cf0b7f10..d6793042b 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -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) { diff --git a/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js index c96d914ab..411fca35a 100644 --- a/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js +++ b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js @@ -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);