From 4866a6d52fe5afef61ef76da4fdafed10c5205f0 Mon Sep 17 00:00:00 2001 From: konsoletyper Date: Sat, 21 Jun 2014 20:33:59 +0400 Subject: [PATCH] Moves test generation logic from teavm-maven-plugin to teavm-core --- .../org/teavm/tooling/EmptyTeaVMToolLog.java | 54 +++ .../java/org/teavm/tooling/TeaVMTestTool.java | 359 ++++++++++++++++++ .../org/teavm/tooling/TeaVMToolException.java | 36 ++ .../java/org/teavm/tooling/TeaVMToolLog.java | 38 ++ .../tooling/TestExceptionDependency.java | 69 ++++ .../teavm/tooling/TestExceptionPlugin.java | 30 ++ .../org/teavm/tooling/test}/junit.html | 0 .../org/teavm/tooling/test}/res/class_obj.png | Bin .../tooling/test}/res/control-000-small.png | Bin .../teavm/tooling/test}/res/junit-support.js | 0 .../org/teavm/tooling/test}/res/junit.css | 0 .../teavm/tooling/test}/res/methpub_obj.png | Bin .../teavm/tooling/test}/res/package_obj.png | Bin .../org/teavm/tooling/test}/res/runtime.js | 0 .../tooling/test}/res/tick-small-red.png | Bin .../teavm/tooling/test}/res/tick-small.png | Bin .../tooling/test}/res/toggle-small-expand.png | Bin .../teavm/tooling/test}/res/toggle-small.png | Bin .../teavm/maven/BuildJavascriptTestMojo.java | 283 ++------------ .../org/teavm/maven/MavenTeaVMToolLog.java | 56 +++ 20 files changed, 667 insertions(+), 258 deletions(-) create mode 100644 teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java create mode 100644 teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java create mode 100644 teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java create mode 100644 teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java create mode 100644 teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java create mode 100644 teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/junit.html (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/class_obj.png (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/control-000-small.png (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/junit-support.js (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/junit.css (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/methpub_obj.png (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/package_obj.png (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/runtime.js (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/tick-small-red.png (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/tick-small.png (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/toggle-small-expand.png (100%) rename {teavm-maven-plugin/src/main/resources/org/teavm/maven => teavm-core/src/main/resources/org/teavm/tooling/test}/res/toggle-small.png (100%) create mode 100644 teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java diff --git a/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java b/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java new file mode 100644 index 000000000..917e73fb4 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 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 class EmptyTeaVMToolLog implements TeaVMToolLog { + @Override + public void info(String text) { + } + + @Override + public void debug(String text) { + } + + @Override + public void warning(String text) { + } + + @Override + public void error(String text) { + } + + @Override + public void info(String text, Throwable e) { + } + + @Override + public void debug(String text, Throwable e) { + } + + @Override + public void warning(String text, Throwable e) { + } + + @Override + public void error(String text, Throwable e) { + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java new file mode 100644 index 000000000..9d70eca42 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java @@ -0,0 +1,359 @@ +/* + * Copyright 2014 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.*; +import java.util.*; +import org.apache.commons.io.IOUtils; +import org.teavm.common.FiniteExecutor; +import org.teavm.common.SimpleFiniteExecutor; +import org.teavm.common.ThreadPoolFiniteExecutor; +import org.teavm.model.*; +import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.testing.JUnitTestAdapter; +import org.teavm.testing.TestAdapter; +import org.teavm.vm.DirectoryBuildTarget; +import org.teavm.vm.TeaVM; +import org.teavm.vm.TeaVMBuilder; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTestTool { + private Map> groupedMethods = new HashMap<>(); + private Map fileNames = new HashMap<>(); + private List testMethods = new ArrayList<>(); + private File outputDir = new File("."); + private boolean minifying = true; + private int numThreads = 1; + private TestAdapter adapter = new JUnitTestAdapter(); + private List transformers = new ArrayList<>(); + private List additionalScripts = new ArrayList<>(); + private List additionalScriptLocalPaths = new ArrayList<>(); + private Properties properties = new Properties(); + private List testClasses = new ArrayList<>(); + private ClassLoader classLoader = TeaVMTestTool.class.getClassLoader(); + private TeaVMToolLog log = new EmptyTeaVMToolLog(); + + public File getOutputDir() { + return outputDir; + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } + + public boolean isMinifying() { + return minifying; + } + + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public int getNumThreads() { + return numThreads; + } + + public void setNumThreads(int numThreads) { + this.numThreads = numThreads; + } + + public TestAdapter getAdapter() { + return adapter; + } + + public void setAdapter(TestAdapter adapter) { + this.adapter = adapter; + } + + public List getTransformers() { + return transformers; + } + + public List getAdditionalScripts() { + return additionalScripts; + } + + public Properties getProperties() { + return properties; + } + + public List getTestClasses() { + return testClasses; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public TeaVMToolLog getLog() { + return log; + } + + public void setLog(TeaVMToolLog log) { + this.log = log; + } + + public void generate() throws TeaVMToolException { + Runnable finalizer = null; + try { + new File(outputDir, "tests").mkdirs(); + new File(outputDir, "res").mkdirs(); + resourceToFile("org/teavm/javascript/runtime.js", "res/runtime.js"); + String prefix = "org/teavm/tooling/test"; + resourceToFile(prefix + "/res/junit-support.js", "res/junit-support.js"); + resourceToFile(prefix + "/res/junit.css", "res/junit.css"); + resourceToFile(prefix + "/res/class_obj.png", "res/class_obj.png"); + resourceToFile(prefix + "/res/control-000-small.png", "res/control-000-small.png"); + resourceToFile(prefix + "/res/methpub_obj.png", "res/methpub_obj.png"); + resourceToFile(prefix + "/res/package_obj.png", "res/package_obj.png"); + resourceToFile(prefix + "/res/tick-small-red.png", "res/tick-small-red.png"); + resourceToFile(prefix + "/res/tick-small.png", "res/tick-small.png"); + resourceToFile(prefix + "/res/toggle-small-expand.png", "res/toggle-small-expand.png"); + resourceToFile(prefix + "/res/toggle-small.png", "res/toggle-small.png"); + resourceToFile(prefix + "/junit.html", "junit.html"); + final ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader); + for (String testClass : testClasses) { + ClassHolder classHolder = classSource.get(testClass); + if (classHolder == null) { + throw new TeaVMToolException("Could not find class " + testClass); + } + findTests(classHolder); + } + + includeAdditionalScripts(classLoader); + File allTestsFile = new File(outputDir, "tests/all.js"); + try (Writer allTestsWriter = new OutputStreamWriter(new FileOutputStream(allTestsFile), "UTF-8")) { + allTestsWriter.write("prepare = function() {\n"); + allTestsWriter.write(" return new JUnitServer(document.body).readTests(["); + boolean first = true; + for (String testClass : testClasses) { + Collection methods = groupedMethods.get(testClass); + if (methods == null) { + continue; + } + if (!first) { + allTestsWriter.append(","); + } + first = false; + allTestsWriter.append("\n { name : \"").append(testClass).append("\", methods : ["); + boolean firstMethod = true; + for (MethodReference methodRef : methods) { + String scriptName = "tests/" + fileNames.size() + ".js"; + fileNames.put(methodRef, scriptName); + if (!firstMethod) { + allTestsWriter.append(","); + } + firstMethod = false; + allTestsWriter.append("\n { name : \"" + methodRef.getName() + "\", script : \"" + + scriptName + "\", expected : ["); + MethodHolder methodHolder = classSource.get(testClass).getMethod( + methodRef.getDescriptor()); + boolean firstException = true; + for (String exception : adapter.getExpectedExceptions(methodHolder)) { + if (!firstException) { + allTestsWriter.append(", "); + } + firstException = false; + allTestsWriter.append("\"" + exception + "\""); + } + allTestsWriter.append("], additionalScripts : ["); + for (int i = 0; i < additionalScriptLocalPaths.size(); ++i) { + if (i > 0) { + allTestsWriter.append(", "); + } + escapeString(additionalScriptLocalPaths.get(i), allTestsWriter); + } + allTestsWriter.append("] }"); + } + allTestsWriter.append("] }"); + } + allTestsWriter.write("], function() {}); }"); + } + int methodsGenerated = 0; + log.info("Generating test files"); + FiniteExecutor executor = new SimpleFiniteExecutor(); + if (numThreads != 1) { + int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); + final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads); + finalizer = new Runnable() { + @Override public void run() { + threadedExecutor.stop(); + } + }; + executor = threadedExecutor; + } + for (final MethodReference method : testMethods) { + executor.execute(new Runnable() { + @Override public void run() { + log.debug("Building test for " + method); + try { + decompileClassesForTest(classLoader, new CopyClassHolderSource(classSource), method, + fileNames.get(method), new SimpleFiniteExecutor()); + } catch (IOException e) { + log.error("Error generating JavaScript", e); + } + } + }); + ++methodsGenerated; + } + executor.complete(); + log.info("Test files successfully generated for " + methodsGenerated + " method(s)."); + } catch (IOException e) { + throw new TeaVMToolException("IO error occured generating JavaScript files", e); + } finally { + if (finalizer != null) { + finalizer.run(); + } + } + } + + private void resourceToFile(String resource, String fileName) throws IOException { + try (InputStream input = TeaVMTestTool.class.getClassLoader().getResourceAsStream(resource)) { + try (OutputStream output = new FileOutputStream(new File(outputDir, fileName))) { + IOUtils.copy(input, output); + } + } + } + + private void findTests(ClassHolder cls) { + for (MethodHolder method : cls.getMethods()) { + if (adapter.acceptMethod(method)) { + MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); + testMethods.add(ref); + List group = groupedMethods.get(cls.getName()); + if (group == null) { + group = new ArrayList<>(); + groupedMethods.put(cls.getName(), group); + } + group.add(ref); + } + } + } + + private void includeAdditionalScripts(ClassLoader classLoader) throws TeaVMToolException { + if (additionalScripts == null) { + return; + } + for (String script : additionalScripts) { + String simpleName = script.substring(script.lastIndexOf('/') + 1); + additionalScriptLocalPaths.add("tests/" + simpleName); + if (classLoader.getResource(script) == null) { + throw new TeaVMToolException("Additional script " + script + " was not found"); + } + File file = new File(outputDir, "tests/" + simpleName); + try (InputStream in = classLoader.getResourceAsStream(script)) { + if (!file.exists()) { + file.createNewFile(); + } + try(OutputStream out = new FileOutputStream(file)) { + IOUtils.copy(in, out); + } + } catch (IOException e) { + throw new TeaVMToolException("Error copying additional script " + script, e); + } + } + } + + private void decompileClassesForTest(ClassLoader classLoader, ClassHolderSource classSource, + MethodReference methodRef, String targetName, FiniteExecutor executor) throws IOException { + TeaVM vm = new TeaVMBuilder() + .setClassLoader(classLoader) + .setClassSource(classSource) + .setExecutor(executor) + .build(); + vm.setProperties(properties); + vm.setMinifying(minifying); + vm.installPlugins(); + new TestExceptionPlugin().install(vm); + for (ClassHolderTransformer transformer : transformers) { + vm.add(transformer); + } + File file = new File(outputDir, targetName); + try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { + MethodReference cons = new MethodReference(methodRef.getClassName(), + new MethodDescriptor("", ValueType.VOID)); + MethodReference exceptionMsg = new MethodReference("java.lang.Throwable", "getMessage", + ValueType.object("java.lang.String")); + vm.entryPoint("initInstance", cons); + vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()); + vm.entryPoint("extractException", exceptionMsg); + vm.exportType("TestClass", cons.getClassName()); + vm.build(innerWriter, new DirectoryBuildTarget(outputDir)); + if (!vm.hasMissingItems()) { + innerWriter.append("\n"); + innerWriter.append("\nJUnitClient.run();"); + innerWriter.close(); + } else { + innerWriter.append("JUnitClient.reportError(\n"); + StringBuilder sb = new StringBuilder(); + vm.showMissingItems(sb); + escapeStringLiteral(sb.toString(), innerWriter); + innerWriter.append(");"); + log.warning("Error building test " + methodRef); + log.warning(sb.toString()); + } + } + } + + private void escapeStringLiteral(String text, Writer writer) throws IOException { + int index = 0; + while (true) { + int next = text.indexOf('\n', index); + if (next < 0) { + break; + } + escapeString(text.substring(index, next + 1), writer); + writer.append(" +\n"); + index = next + 1; + } + escapeString(text.substring(index), writer); + } + + private void escapeString(String string, Writer writer) throws IOException { + writer.append('\"'); + for (int i = 0; i < string.length(); ++i) { + char c = string.charAt(i); + switch (c) { + case '"': + writer.append("\\\""); + break; + case '\\': + writer.append("\\\\"); + break; + case '\n': + writer.append("\\n"); + break; + case '\r': + writer.append("\\r"); + break; + case '\t': + writer.append("\\t"); + break; + default: + writer.append(c); + break; + } + } + writer.append('\"'); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java new file mode 100644 index 000000000..510006be6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 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 class TeaVMToolException extends Exception { + private static final long serialVersionUID = 579149191624783241L; + + public TeaVMToolException(String message, Throwable cause) { + super(message, cause); + } + + public TeaVMToolException(String message) { + super(message); + } + + public TeaVMToolException(Throwable cause) { + super(cause); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java new file mode 100644 index 000000000..bc1ee5b81 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 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 TeaVMToolLog { + void info(String text); + + void debug(String text); + + void warning(String text); + + void error(String text); + + void info(String text, Throwable e); + + void debug(String text, Throwable e); + + void warning(String text, Throwable e); + + void error(String text, Throwable e); +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java new file mode 100644 index 000000000..bf447b030 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014 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 org.teavm.dependency.*; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +class TestExceptionDependency implements DependencyListener { + private MethodReference getMessageRef = new MethodReference("java.lang.Throwable", "getMessage", + ValueType.object("java.lang.String")); + private DependencyNode allClasses; + + @Override + public void started(DependencyAgent agent) { + allClasses = agent.createNode(); + } + + @Override + public void classAchieved(DependencyAgent agent, String className) { + if (isException(agent.getClassSource(), className)) { + allClasses.propagate(className); + } + } + + private boolean isException(ClassReaderSource classSource, String className) { + while (className != null) { + if (className.equals("java.lang.Throwable")) { + return true; + } + ClassReader cls = classSource.get(className); + if (cls == null) { + return false; + } + className = cls.getParent(); + } + return false; + } + + @Override + public void methodAchieved(DependencyAgent agent, MethodDependency method) { + if (method.getReference().equals(getMessageRef)) { + allClasses.connect(method.getVariable(0)); + } + } + + @Override + public void fieldAchieved(DependencyAgent agent, FieldDependency field) { + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java new file mode 100644 index 000000000..2ec7136c6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 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 org.teavm.vm.spi.TeaVMHost; +import org.teavm.vm.spi.TeaVMPlugin; + +/** + * + * @author Alexey Andreev + */ +class TestExceptionPlugin implements TeaVMPlugin { + @Override + public void install(TeaVMHost host) { + host.add(new TestExceptionDependency()); + } +} diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/junit.html b/teavm-core/src/main/resources/org/teavm/tooling/test/junit.html similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/junit.html rename to teavm-core/src/main/resources/org/teavm/tooling/test/junit.html diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/class_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/class_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/class_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/class_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/control-000-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/control-000-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/control-000-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/control-000-small.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit-support.js b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit-support.js rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit.css b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit.css similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit.css rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/junit.css diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/methpub_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/methpub_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/methpub_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/methpub_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/package_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/package_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/package_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/package_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/runtime.js b/teavm-core/src/main/resources/org/teavm/tooling/test/res/runtime.js similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/runtime.js rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/runtime.js diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small-red.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small-red.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small-red.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small-red.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small-expand.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small-expand.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small-expand.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small-expand.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small.png diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java index 1bb62251d..6702cc11c 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java @@ -15,7 +15,8 @@ */ package org.teavm.maven; -import java.io.*; +import java.io.File; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; @@ -25,7 +26,6 @@ import java.util.*; 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.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; @@ -36,16 +36,11 @@ import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.project.MavenProject; -import org.teavm.common.FiniteExecutor; -import org.teavm.common.SimpleFiniteExecutor; -import org.teavm.common.ThreadPoolFiniteExecutor; -import org.teavm.model.*; -import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.model.ClassHolderTransformer; import org.teavm.testing.JUnitTestAdapter; import org.teavm.testing.TestAdapter; -import org.teavm.vm.DirectoryBuildTarget; -import org.teavm.vm.TeaVM; -import org.teavm.vm.TeaVMBuilder; +import org.teavm.tooling.TeaVMTestTool; +import org.teavm.tooling.TeaVMToolException; /** * @@ -57,11 +52,6 @@ public class BuildJavascriptTestMojo extends AbstractMojo { private static Set testScopes = new HashSet<>(Arrays.asList( Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME, Artifact.SCOPE_PROVIDED)); - private Map> groupedMethods = new HashMap<>(); - private Map fileNames = new HashMap<>(); - private List testMethods = new ArrayList<>(); - private List testClasses = new ArrayList<>(); - private TestAdapter adapter; @Component private MavenProject project; @@ -93,16 +83,14 @@ public class BuildJavascriptTestMojo extends AbstractMojo { @Parameter private String[] transformers; - private List transformerInstances; - @Parameter private String[] additionalScripts; - private List additionalScriptLocalPaths = new ArrayList<>(); - @Parameter private Properties properties; + private TeaVMTestTool tool = new TeaVMTestTool(); + public void setProject(MavenProject project) { this.project = project; } @@ -154,124 +142,33 @@ public class BuildJavascriptTestMojo extends AbstractMojo { getLog().info("Tests build skipped as specified by system property"); return; } - Runnable finalizer = null; try { final ClassLoader classLoader = prepareClassLoader(); - createAdapter(classLoader); getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'"); + tool.setClassLoader(classLoader); + tool.setAdapter(createAdapter(classLoader)); findTestClasses(classLoader, testFiles, ""); if (scanDependencies) { findTestsInDependencies(classLoader); } - final Log log = getLog(); - new File(outputDir, "tests").mkdirs(); - new File(outputDir, "res").mkdirs(); - resourceToFile("org/teavm/javascript/runtime.js", "res/runtime.js"); - resourceToFile("org/teavm/maven/res/junit-support.js", "res/junit-support.js"); - resourceToFile("org/teavm/maven/res/junit.css", "res/junit.css"); - resourceToFile("org/teavm/maven/res/class_obj.png", "res/class_obj.png"); - resourceToFile("org/teavm/maven/res/control-000-small.png", "res/control-000-small.png"); - resourceToFile("org/teavm/maven/res/methpub_obj.png", "res/methpub_obj.png"); - resourceToFile("org/teavm/maven/res/package_obj.png", "res/package_obj.png"); - resourceToFile("org/teavm/maven/res/tick-small-red.png", "res/tick-small-red.png"); - resourceToFile("org/teavm/maven/res/tick-small.png", "res/tick-small.png"); - resourceToFile("org/teavm/maven/res/toggle-small-expand.png", "res/toggle-small-expand.png"); - resourceToFile("org/teavm/maven/res/toggle-small.png", "res/toggle-small.png"); - resourceToFile("org/teavm/maven/junit.html", "junit.html"); - final ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader); - for (String testClass : testClasses) { - ClassHolder classHolder = classSource.get(testClass); - if (classHolder == null) { - throw new MojoFailureException("Could not find class " + testClass); - } - findTests(classHolder); + tool.getTransformers().addAll(instantiateTransformers(classLoader)); + tool.setLog(new MavenTeaVMToolLog(getLog())); + tool.setOutputDir(outputDir); + tool.setNumThreads(numThreads); + tool.setMinifying(minifying); + if (properties != null) { + tool.getProperties().putAll(properties); } - - transformerInstances = instantiateTransformers(classLoader); - includeAdditionalScripts(classLoader); - File allTestsFile = new File(outputDir, "tests/all.js"); - try (Writer allTestsWriter = new OutputStreamWriter(new FileOutputStream(allTestsFile), "UTF-8")) { - allTestsWriter.write("prepare = function() {\n"); - allTestsWriter.write(" return new JUnitServer(document.body).readTests(["); - boolean first = true; - for (String testClass : testClasses) { - if (!first) { - allTestsWriter.append(","); - } - first = false; - allTestsWriter.append("\n { name : \"").append(testClass).append("\", methods : ["); - boolean firstMethod = true; - for (MethodReference methodRef : groupedMethods.get(testClass)) { - String scriptName = "tests/" + fileNames.size() + ".js"; - fileNames.put(methodRef, scriptName); - if (!firstMethod) { - allTestsWriter.append(","); - } - firstMethod = false; - allTestsWriter.append("\n { name : \"" + methodRef.getName() + "\", script : \"" + - scriptName + "\", expected : ["); - MethodHolder methodHolder = classSource.get(testClass).getMethod( - methodRef.getDescriptor()); - boolean firstException = true; - for (String exception : adapter.getExpectedExceptions(methodHolder)) { - if (!firstException) { - allTestsWriter.append(", "); - } - firstException = false; - allTestsWriter.append("\"" + exception + "\""); - } - allTestsWriter.append("], additionalScripts : ["); - for (int i = 0; i < additionalScriptLocalPaths.size(); ++i) { - if (i > 0) { - allTestsWriter.append(", "); - } - escapeString(additionalScriptLocalPaths.get(i), allTestsWriter); - } - allTestsWriter.append("] }"); - } - allTestsWriter.append("] }"); - } - allTestsWriter.write("], function() {}); }"); - } - int methodsGenerated = 0; - log.info("Generating test files"); - FiniteExecutor executor = new SimpleFiniteExecutor(); - if (numThreads != 1) { - int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); - final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads); - finalizer = new Runnable() { - @Override public void run() { - threadedExecutor.stop(); - } - }; - executor = threadedExecutor; - } - for (final MethodReference method : testMethods) { - executor.execute(new Runnable() { - @Override public void run() { - log.debug("Building test for " + method); - try { - decompileClassesForTest(classLoader, new CopyClassHolderSource(classSource), method, - fileNames.get(method), new SimpleFiniteExecutor()); - } catch (IOException e) { - log.error("Error generating JavaScript", e); - } - } - }); - ++methodsGenerated; - } - executor.complete(); - log.info("Test files successfully generated for " + methodsGenerated + " method(s)."); - } catch (IOException e) { - throw new MojoFailureException("IO error occured generating JavaScript files", e); - } finally { - if (finalizer != null) { - finalizer.run(); + if (additionalScripts != null) { + tool.getAdditionalScripts().addAll(Arrays.asList(additionalScripts)); } + tool.generate(); + } catch (TeaVMToolException e) { + throw new MojoFailureException("Error occured generating JavaScript files", e); } } - private void createAdapter(ClassLoader classLoader) throws MojoExecutionException { + private TestAdapter createAdapter(ClassLoader classLoader) throws MojoExecutionException { Class adapterClsRaw; try { adapterClsRaw = Class.forName(adapterClass, true, classLoader); @@ -290,7 +187,7 @@ public class BuildJavascriptTestMojo extends AbstractMojo { throw new MojoExecutionException("No default constructor found for test adapter " + adapterClass, e); } try { - adapter = cons.newInstance(); + return cons.newInstance(); } catch (IllegalAccessException | InstantiationException e) { throw new MojoExecutionException("Error creating test adapter", e); } catch (InvocationTargetException e) { @@ -330,89 +227,6 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } } - private void decompileClassesForTest(ClassLoader classLoader, ClassHolderSource classSource, - MethodReference methodRef, String targetName, FiniteExecutor executor) throws IOException { - TeaVM vm = new TeaVMBuilder() - .setClassLoader(classLoader) - .setClassSource(classSource) - .setExecutor(executor) - .build(); - vm.setProperties(properties); - vm.setMinifying(minifying); - vm.installPlugins(); - new TestExceptionPlugin().install(vm); - for (ClassHolderTransformer transformer : transformerInstances) { - vm.add(transformer); - } - File file = new File(outputDir, targetName); - try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { - MethodReference cons = new MethodReference(methodRef.getClassName(), - new MethodDescriptor("", ValueType.VOID)); - MethodReference exceptionMsg = new MethodReference("java.lang.Throwable", "getMessage", - ValueType.object("java.lang.String")); - vm.entryPoint("initInstance", cons); - vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()); - vm.entryPoint("extractException", exceptionMsg); - vm.exportType("TestClass", cons.getClassName()); - vm.build(innerWriter, new DirectoryBuildTarget(outputDir)); - if (!vm.hasMissingItems()) { - innerWriter.append("\n"); - innerWriter.append("\nJUnitClient.run();"); - innerWriter.close(); - } else { - innerWriter.append("JUnitClient.reportError(\n"); - StringBuilder sb = new StringBuilder(); - vm.showMissingItems(sb); - escapeStringLiteral(sb.toString(), innerWriter); - innerWriter.append(");"); - getLog().warn("Error building test " + methodRef); - getLog().warn(sb); - } - } - } - - private void escapeStringLiteral(String text, Writer writer) throws IOException { - int index = 0; - while (true) { - int next = text.indexOf('\n', index); - if (next < 0) { - break; - } - escapeString(text.substring(index, next + 1), writer); - writer.append(" +\n"); - index = next + 1; - } - escapeString(text.substring(index), writer); - } - - private void escapeString(String string, Writer writer) throws IOException { - writer.append('\"'); - for (int i = 0; i < string.length(); ++i) { - char c = string.charAt(i); - switch (c) { - case '"': - writer.append("\\\""); - break; - case '\\': - writer.append("\\\\"); - break; - case '\n': - writer.append("\\n"); - break; - case '\r': - writer.append("\\r"); - break; - case '\t': - writer.append("\\t"); - break; - default: - writer.append(c); - break; - } - } - writer.append('\"'); - } - private void findTestsInDependencies(ClassLoader classLoader) throws MojoExecutionException { try { Log log = getLog(); @@ -477,8 +291,8 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } try { Class candidate = Class.forName(className, true, classLoader); - if (adapter.acceptClass(candidate)) { - testClasses.add(candidate.getName()); + if (tool.getAdapter().acceptClass(candidate)) { + tool.getTestClasses().add(candidate.getName()); getLog().info("Test class detected: " + candidate.getName()); } } catch (ClassNotFoundException e) { @@ -486,28 +300,6 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } } - private void findTests(ClassHolder cls) { - for (MethodHolder method : cls.getMethods()) { - if (adapter.acceptMethod(method)) { - MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); - testMethods.add(ref); - List group = groupedMethods.get(cls.getName()); - if (group == null) { - group = new ArrayList<>(); - groupedMethods.put(cls.getName(), group); - } - group.add(ref); - } - } - } - - private void resourceToFile(String resource, String fileName) throws IOException { - try (InputStream input = BuildJavascriptTestMojo.class.getClassLoader().getResourceAsStream(resource)) { - try (OutputStream output = new FileOutputStream(new File(outputDir, fileName))) { - IOUtils.copy(input, output); - } - } - } private List instantiateTransformers(ClassLoader classLoader) throws MojoExecutionException { @@ -543,29 +335,4 @@ public class BuildJavascriptTestMojo extends AbstractMojo { } return transformerInstances; } - - private void includeAdditionalScripts(ClassLoader classLoader) throws MojoExecutionException { - if (additionalScripts == null) { - return; - } - for (String script : additionalScripts) { - String simpleName = script.substring(script.lastIndexOf('/') + 1); - additionalScriptLocalPaths.add("tests/" + simpleName); - if (classLoader.getResource(script) == null) { - throw new MojoExecutionException("Additional script " + script + " was not found"); - } - File file = new File(outputDir, "tests/" + simpleName); - try (InputStream in = classLoader.getResourceAsStream(script)) { - if (!file.exists()) { - file.createNewFile(); - } - try(OutputStream out = new FileOutputStream(file)) { - IOUtils.copy(in, out); - } - } catch (IOException e) { - throw new MojoExecutionException("Error copying additional script " + script, e); - } - } - } } - diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java new file mode 100644 index 000000000..657e69e28 --- /dev/null +++ b/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java @@ -0,0 +1,56 @@ +package org.teavm.maven; + +import org.apache.maven.plugin.logging.Log; +import org.teavm.tooling.TeaVMToolLog; + +/** + * + * @author Alexey Andreev + */ +public class MavenTeaVMToolLog implements TeaVMToolLog { + private Log log; + + public MavenTeaVMToolLog(Log log) { + this.log = log; + } + + @Override + public void info(String text) { + log.info(text); + } + + @Override + public void debug(String text) { + log.debug(text); + } + + @Override + public void warning(String text) { + log.warn(text); + } + + @Override + public void error(String text) { + log.error(text); + } + + @Override + public void info(String text, Throwable e) { + log.info(text, e); + } + + @Override + public void debug(String text, Throwable e) { + log.debug(text, e); + } + + @Override + public void warning(String text, Throwable e) { + log.warn(text, e); + } + + @Override + public void error(String text, Throwable e) { + log.error(text, e); + } +}