Moves test generation logic from teavm-maven-plugin to teavm-core

This commit is contained in:
konsoletyper 2014-06-21 20:33:59 +04:00
parent 2e5cdc109b
commit 4866a6d52f
20 changed files with 667 additions and 258 deletions

View File

@ -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 <konsoletyper@gmail.com>
*/
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) {
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
public class TeaVMTestTool {
private Map<String, List<MethodReference>> groupedMethods = new HashMap<>();
private Map<MethodReference, String> fileNames = new HashMap<>();
private List<MethodReference> testMethods = new ArrayList<>();
private File outputDir = new File(".");
private boolean minifying = true;
private int numThreads = 1;
private TestAdapter adapter = new JUnitTestAdapter();
private List<ClassHolderTransformer> transformers = new ArrayList<>();
private List<String> additionalScripts = new ArrayList<>();
private List<String> additionalScriptLocalPaths = new ArrayList<>();
private Properties properties = new Properties();
private List<String> 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<ClassHolderTransformer> getTransformers() {
return transformers;
}
public List<String> getAdditionalScripts() {
return additionalScripts;
}
public Properties getProperties() {
return properties;
}
public List<String> 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<MethodReference> 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<MethodReference> 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("<init>", 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('\"');
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}
}

View File

@ -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 <konsoletyper@gmail.com>
*/
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);
}

View File

@ -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) {
}
}

View File

@ -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());
}
}

View File

@ -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<String> testScopes = new HashSet<>(Arrays.asList(
Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME,
Artifact.SCOPE_PROVIDED));
private Map<String, List<MethodReference>> groupedMethods = new HashMap<>();
private Map<MethodReference, String> fileNames = new HashMap<>();
private List<MethodReference> testMethods = new ArrayList<>();
private List<String> 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<ClassHolderTransformer> transformerInstances;
@Parameter
private String[] additionalScripts;
private List<String> 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);
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);
}
findTests(classHolder);
}
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("<init>", 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<MethodReference> 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<ClassHolderTransformer> 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);
}
}
}
}

View File

@ -0,0 +1,56 @@
package org.teavm.maven;
import org.apache.maven.plugin.logging.Log;
import org.teavm.tooling.TeaVMToolLog;
/**
*
* @author Alexey Andreev <konsoletyper@gmail.com>
*/
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);
}
}