org.teavm.tooling.TeaVMTestTool Maven / Gradle / Ivy
/*
* 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.debugging.information.DebugInformation;
import org.teavm.debugging.information.DebugInformationBuilder;
import org.teavm.javascript.EmptyRegularMethodNodeCache;
import org.teavm.javascript.InMemoryRegularMethodNodeCache;
import org.teavm.javascript.MethodNodeCache;
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();
private boolean debugInformationGenerated;
private boolean sourceMapsGenerated;
private boolean sourceFilesCopied;
private boolean incremental;
private List sourceFileProviders = new ArrayList<>();
private MethodNodeCache astCache;
private ProgramCache programCache;
private SourceFilesCopier sourceFilesCopier;
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 boolean isIncremental() {
return incremental;
}
public void setIncremental(boolean incremental) {
this.incremental = incremental;
}
public boolean isDebugInformationGenerated() {
return debugInformationGenerated;
}
public void setDebugInformationGenerated(boolean debugInformationGenerated) {
this.debugInformationGenerated = debugInformationGenerated;
}
public boolean isSourceMapsGenerated() {
return sourceMapsGenerated;
}
public void setSourceMapsGenerated(boolean sourceMapsGenerated) {
this.sourceMapsGenerated = sourceMapsGenerated;
}
public boolean isSourceFilesCopied() {
return sourceFilesCopied;
}
public void setSourceFilesCopied(boolean sourceFilesCopied) {
this.sourceFilesCopied = sourceFilesCopied;
}
public void addSourceFileProvider(SourceFileProvider sourceFileProvider) {
sourceFileProviders.add(sourceFileProvider);
}
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");
ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader);
if (incremental) {
classSource = new PreOptimizingClassHolderSource(classSource);
}
for (String testClass : testClasses) {
ClassHolder classHolder = classSource.get(testClass);
if (classHolder == null) {
throw new TeaVMToolException("Could not find class " + testClass);
}
findTests(classHolder);
}
includeAdditionalScripts(classLoader);
astCache = new EmptyRegularMethodNodeCache();
if (incremental) {
astCache = new InMemoryRegularMethodNodeCache();
programCache = new InMemoryProgramCache();
}
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");
sourceFilesCopier = new SourceFilesCopier(sourceFileProviders);
sourceFilesCopier.setLog(log);
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) {
final ClassHolderSource builderClassSource = classSource;
executor.execute(new Runnable() {
@Override public void run() {
log.debug("Building test for " + method);
try {
decompileClassesForTest(classLoader, new CopyClassHolderSource(builderClassSource), method,
fileNames.get(method));
} catch (IOException e) {
log.error("Error generating JavaScript", e);
}
}
});
++methodsGenerated;
}
executor.complete();
if (sourceFilesCopied) {
sourceFilesCopier.copy(new File(new File(outputDir, "tests"), "src"));
}
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) throws IOException {
TeaVM vm = new TeaVMBuilder()
.setClassLoader(classLoader)
.setClassSource(classSource)
.build();
vm.setIncremental(incremental);
vm.setAstCache(astCache);
vm.setProgramCache(programCache);
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);
DebugInformationBuilder debugInfoBuilder = sourceMapsGenerated || debugInformationGenerated ?
new DebugInformationBuilder() : null;
try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) {
MethodReference cons = new MethodReference(methodRef.getClassName(), "", ValueType.VOID);
MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException",
Throwable.class, String.class);
vm.entryPoint("initInstance", cons);
vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()).async();
vm.entryPoint("extractException", exceptionMsg);
vm.exportType("TestClass", cons.getClassName());
vm.setDebugEmitter(debugInfoBuilder);
vm.build(innerWriter, new DirectoryBuildTarget(outputDir));
innerWriter.append("\n");
innerWriter.append("\nJUnitClient.run();");
if (sourceMapsGenerated) {
String sourceMapsFileName = targetName.substring(targetName.lastIndexOf('/') + 1) + ".map";
innerWriter.append("\n//# sourceMappingURL=").append(sourceMapsFileName);
}
if (!vm.getProblemProvider().getProblems().isEmpty()) {
if (vm.getProblemProvider().getSevereProblems().isEmpty()) {
log.warning("Test built with warnings: " + methodRef);
TeaVMProblemRenderer.describeProblems(vm, log);
} else {
log.warning("Test built with errors: " + methodRef);
TeaVMProblemRenderer.describeProblems(vm, log);
}
}
}
if (sourceMapsGenerated) {
DebugInformation debugInfo = debugInfoBuilder.getDebugInformation();
try (OutputStream debugInfoOut = new FileOutputStream(new File(outputDir, targetName + ".teavmdbg"))) {
debugInfo.write(debugInfoOut);
}
}
if (sourceMapsGenerated) {
DebugInformation debugInfo = debugInfoBuilder.getDebugInformation();
String sourceMapsFileName = targetName + ".map";
try (Writer sourceMapsOut = new OutputStreamWriter(new FileOutputStream(
new File(outputDir, sourceMapsFileName)), "UTF-8")) {
debugInfo.writeAsSourceMaps(sourceMapsOut, "src", targetName);
}
}
if (sourceFilesCopied && vm.getWrittenClasses() != null) {
sourceFilesCopier.addClasses(vm.getWrittenClasses());
}
}
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('\"');
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy