From 6f173003b3274f917c2ac139f4f37382179bd196 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 7 Oct 2015 19:16:11 +0300 Subject: [PATCH] Developing selenium JUnit test runner --- .../java/org/teavm/tooling/TeaVMTestCase.java | 46 +++++++++ .../java/org/teavm/tooling/TeaVMTestTool.java | 23 ++++- .../teavm/tooling/TeaVMTestToolListener.java | 24 +++++ pom.xml | 15 +-- tests/pom.xml | 1 + tools/maven/plugin/pom.xml | 10 ++ .../org/teavm/maven/BuildJavascriptMojo.java | 22 ++++- .../teavm/maven/BuildJavascriptTestMojo.java | 96 ++++++++++++++++++- .../main/resources/teavm-selenium-adapter.js | 48 ++++++++++ .../src/main/resources/teavm-selenium.js | 40 ++++++++ 10 files changed, 314 insertions(+), 11 deletions(-) create mode 100644 core/src/main/java/org/teavm/tooling/TeaVMTestCase.java create mode 100644 core/src/main/java/org/teavm/tooling/TeaVMTestToolListener.java create mode 100644 tools/maven/plugin/src/main/resources/teavm-selenium-adapter.js create mode 100644 tools/maven/plugin/src/main/resources/teavm-selenium.js diff --git a/core/src/main/java/org/teavm/tooling/TeaVMTestCase.java b/core/src/main/java/org/teavm/tooling/TeaVMTestCase.java new file mode 100644 index 000000000..6ab2f7713 --- /dev/null +++ b/core/src/main/java/org/teavm/tooling/TeaVMTestCase.java @@ -0,0 +1,46 @@ +/* + * 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.tooling; + +import java.io.File; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTestCase { + private File runtimeScript; + private File testScript; + private File debugTable; + + public TeaVMTestCase(File runtimeScript, File testScript, File debugTable) { + this.runtimeScript = runtimeScript; + this.testScript = testScript; + this.debugTable = debugTable; + } + + public File getRuntimeScript() { + return runtimeScript; + } + + public File getTestScript() { + return testScript; + } + + public File getDebugTable() { + return debugTable; + } +} diff --git a/core/src/main/java/org/teavm/tooling/TeaVMTestTool.java b/core/src/main/java/org/teavm/tooling/TeaVMTestTool.java index a2895b0eb..ff57ddaf6 100644 --- a/core/src/main/java/org/teavm/tooling/TeaVMTestTool.java +++ b/core/src/main/java/org/teavm/tooling/TeaVMTestTool.java @@ -61,6 +61,7 @@ public class TeaVMTestTool { private MethodNodeCache astCache; private ProgramCache programCache; private SourceFilesCopier sourceFilesCopier; + private List listeners = new ArrayList<>(); public File getOutputDir() { return outputDir; @@ -347,6 +348,7 @@ public class TeaVMTestTool { for (ClassHolderTransformer transformer : transformers) { vm.add(transformer); } + File file = new File(outputDir, targetName); DebugInformationBuilder debugInfoBuilder = sourceMapsGenerated || debugInformationGenerated ? new DebugInformationBuilder() : null; @@ -376,12 +378,16 @@ public class TeaVMTestTool { } } } - if (sourceMapsGenerated) { + + File debugTableFile = null; + if (debugInformationGenerated) { DebugInformation debugInfo = debugInfoBuilder.getDebugInformation(); - try (OutputStream debugInfoOut = new FileOutputStream(new File(outputDir, targetName + ".teavmdbg"))) { + debugTableFile = new File(outputDir, targetName + ".teavmdbg"); + try (OutputStream debugInfoOut = new FileOutputStream(debugTableFile)) { debugInfo.write(debugInfoOut); } } + if (sourceMapsGenerated) { DebugInformation debugInfo = debugInfoBuilder.getDebugInformation(); String sourceMapsFileName = targetName + ".map"; @@ -393,6 +399,11 @@ public class TeaVMTestTool { if (sourceFilesCopied && vm.getWrittenClasses() != null) { sourceFilesCopier.addClasses(vm.getWrittenClasses()); } + + TeaVMTestCase testCase = new TeaVMTestCase(new File(outputDir, "res/runtime.js"), file, debugTableFile); + for (TeaVMTestToolListener listener : listeners) { + listener.testGenerated(testCase); + } } private void escapeString(String string, Writer writer) throws IOException { @@ -422,4 +433,12 @@ public class TeaVMTestTool { } writer.append('\"'); } + + public void addListener(TeaVMTestToolListener listener) { + listeners.add(listener); + } + + public void removeListener(TeaVMTestToolListener listener) { + listeners.remove(listener); + } } diff --git a/core/src/main/java/org/teavm/tooling/TeaVMTestToolListener.java b/core/src/main/java/org/teavm/tooling/TeaVMTestToolListener.java new file mode 100644 index 000000000..68f87a267 --- /dev/null +++ b/core/src/main/java/org/teavm/tooling/TeaVMTestToolListener.java @@ -0,0 +1,24 @@ +/* + * 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.tooling; + +/** + * + * @author Alexey Andreev + */ +public interface TeaVMTestToolListener { + void testGenerated(TeaVMTestCase testCase); +} diff --git a/pom.xml b/pom.xml index e10fe10f4..f84f2ee34 100644 --- a/pom.xml +++ b/pom.xml @@ -69,7 +69,7 @@ 1.2 9.2.1.v20140609 1.7.7 - 1.9.3 + 2.47.2 @@ -166,13 +166,16 @@ slf4j-api ${slf4j.version} - diff --git a/tests/pom.xml b/tests/pom.xml index f7a6d202d..7614f5da9 100644 --- a/tests/pom.xml +++ b/tests/pom.xml @@ -87,6 +87,7 @@ en, en_US, en_GB, ru, ru_RU ${teavm.test.incremental} + http://127.0.0.1:4444/wd/hub diff --git a/tools/maven/plugin/pom.xml b/tools/maven/plugin/pom.xml index 143dd1213..3d3080b27 100644 --- a/tools/maven/plugin/pom.xml +++ b/tools/maven/plugin/pom.xml @@ -57,6 +57,16 @@ commons-io commons-io + + org.seleniumhq.selenium + selenium-java + 2.47.2 + + + org.seleniumhq.selenium + selenium-remote-driver + 2.47.2 + junit junit diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java index f4c43ec6a..42c47edbf 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java @@ -21,7 +21,12 @@ import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.repository.MavenArtifactRepository; import org.apache.maven.plugin.AbstractMojo; @@ -34,7 +39,12 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.repository.RepositorySystem; import org.teavm.model.ClassHolderTransformer; -import org.teavm.tooling.*; +import org.teavm.tooling.ClassAlias; +import org.teavm.tooling.MethodAlias; +import org.teavm.tooling.RuntimeCopyOperation; +import org.teavm.tooling.SourceFileProvider; +import org.teavm.tooling.TeaVMTool; +import org.teavm.tooling.TeaVMToolException; /** * @@ -149,6 +159,14 @@ public class BuildJavascriptMojo extends AbstractMojo { this.mainPageIncluded = mainPageIncluded; } + public void setCompileScopes(List compileScopes) { + this.compileScopes = compileScopes; + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + public String[] getTransformers() { return transformers; } diff --git a/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java b/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java index a194b57ad..34c3103d1 100644 --- a/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java +++ b/tools/maven/plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java @@ -16,16 +16,28 @@ package org.teavm.maven; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.jar.JarEntry; import java.util.jar.JarFile; import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.repository.MavenArtifactRepository; import org.apache.maven.plugin.AbstractMojo; @@ -38,10 +50,16 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; import org.apache.maven.repository.RepositorySystem; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.remote.RemoteWebDriver; import org.teavm.model.ClassHolderTransformer; import org.teavm.testing.JUnitTestAdapter; import org.teavm.testing.TestAdapter; import org.teavm.tooling.SourceFileProvider; +import org.teavm.tooling.TeaVMTestCase; import org.teavm.tooling.TeaVMTestTool; import org.teavm.tooling.TeaVMToolException; @@ -119,7 +137,13 @@ public class BuildJavascriptTestMojo extends AbstractMojo { @Parameter private boolean sourceFilesCopied; + @Parameter + private URL seleniumURL; + private WebDriver webDriver; + private TeaVMTestTool tool = new TeaVMTestTool(); + private BlockingQueue seleniumTaskQueue = new LinkedBlockingQueue<>(); + private volatile boolean seleniumStopped = false; public void setProject(MavenProject project) { this.project = project; @@ -197,6 +221,10 @@ public class BuildJavascriptTestMojo extends AbstractMojo { this.sourceFilesCopied = sourceFilesCopied; } + public void setSeleniumURL(URL seleniumURL) { + this.seleniumURL = seleniumURL; + } + @Override public void execute() throws MojoExecutionException, MojoFailureException { if (System.getProperty("maven.test.skip", "false").equals("true") || @@ -204,6 +232,8 @@ public class BuildJavascriptTestMojo extends AbstractMojo { getLog().info("Tests build skipped as specified by system property"); return; } + + detectSelenium(); try { final ClassLoader classLoader = prepareClassLoader(); getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'"); @@ -239,9 +269,13 @@ public class BuildJavascriptTestMojo extends AbstractMojo { if (additionalScripts != null) { tool.getAdditionalScripts().addAll(Arrays.asList(additionalScripts)); } + tool.addListener(testCase -> runSelenium(testCase)); tool.generate(); } catch (TeaVMToolException e) { throw new MojoFailureException("Error occured generating JavaScript files", e); + } finally { + webDriver.close(); + stopSelenium(); } } @@ -419,4 +453,64 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } return transformerInstances; } + + private void detectSelenium() { + if (seleniumURL == null) { + return; + } + ChromeDriver driver = new ChromeDriver(); + webDriver = driver; + new Thread(() -> { + while (!seleniumStopped) { + Runnable task; + try { + task = seleniumTaskQueue.poll(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + break; + } + task.run(); + } + }).start(); + } + + private void addSeleniumTask(Runnable runnable) { + if (seleniumURL != null) { + seleniumTaskQueue.add(runnable); + } + } + + private void stopSelenium() { + addSeleniumTask(() -> seleniumStopped = true); + } + + private void runSelenium(TeaVMTestCase testCase) { + if (webDriver == null) { + return; + } + try { + JavascriptExecutor js = (JavascriptExecutor) webDriver; + js.executeAsyncScript( + readResource("teavm-selenium.js"), + readFile(testCase.getRuntimeScript()), + readFile(testCase.getTestScript()), + readResource("teavm-selenium-adapter.js")); + } catch (IOException e) { + getLog().error(e); + } + } + + private String readFile(File file) throws IOException { + try (InputStream input = new FileInputStream(file)) { + return IOUtils.toString(input, "UTF-8"); + } + } + + private String readResource(String resourceName) throws IOException { + try (InputStream input = BuildJavascriptTestMojo.class.getClassLoader().getResourceAsStream(resourceName)) { + if (input == null) { + return ""; + } + return IOUtils.toString(input, "UTF-8"); + } + } } diff --git a/tools/maven/plugin/src/main/resources/teavm-selenium-adapter.js b/tools/maven/plugin/src/main/resources/teavm-selenium-adapter.js new file mode 100644 index 000000000..d29744f72 --- /dev/null +++ b/tools/maven/plugin/src/main/resources/teavm-selenium-adapter.js @@ -0,0 +1,48 @@ +JUnitClient.run = function() { + var handler = window.addEventListener("message", $rt_threadStarter(function() { + var thread = $rt_nativeThread(); + var instance; + var ptr = 0; + var message; + if (thread.isResuming()) { + ptr = thread.pop(); + instance = thread.pop(); + } + loop: while (true) { switch (ptr) { + case 0: + instance = new TestClass(); + ptr = 1; + case 1: + try { + initInstance(instance); + } catch (e) { + message = {}; + JUnitClient.makeErrorMessage(message, e); + break loop; + } + if (thread.isSuspending()) { + thread.push(instance); + thread.push(ptr); + return; + } + ptr = 2; + case 2: + try { + runTest(instance); + } catch (e) { + message = {}; + JUnitClient.makeErrorMessage(message, e); + break loop; + } + if (thread.isSuspending()) { + thread.push(instance); + thread.push(ptr); + return; + } + message = {}; + message.status = "ok"; + break loop; + }} + window.parent.postMessage(JSON.stringify(message), "*"); + })); +} \ No newline at end of file diff --git a/tools/maven/plugin/src/main/resources/teavm-selenium.js b/tools/maven/plugin/src/main/resources/teavm-selenium.js new file mode 100644 index 000000000..3afcaf999 --- /dev/null +++ b/tools/maven/plugin/src/main/resources/teavm-selenium.js @@ -0,0 +1,40 @@ +var runtimeSource = arguments[0] +var testSource = arguments[1] +var adapterSource = arguments[2] + +var iframe = document.createElement("iframe") +document.appendChild(iframe) +var doc = iframe.contentDocument + +loadScripts([ runtimeSource, adapterSource, testSource ], runTest) +window.addEventListener("message", handleMessage) + +function handleMessage(event) { + window.removeEventListener("message", handleMessage) + callback(JSON.stringify(message.data)) +} + +var handler = window.addEventListener("message", function(event) { + window.removeEventListener +}) + +function loadScript(script, callback) { + var elem = doc.createElement("script") + elem.setAttribute("type", "text/javascript") + elem.appendChild(doc.createTextNode(runtimeSource)) + elem.onload = function() { + callback() + } + doc.body.appendChild(script) +} + +function loadScripts(scripts, callback, index) { + index = index || 0 + loadScript(scripts[i], function() { + if (++index == scripts.length) { + callback() + } else { + loadScripts(scripts, callback, index) + } + }) +} \ No newline at end of file