Add pluggable test runner. Add html4j adapter that relaunches test

several times if it throws InterruptedException. Attempting to fix
decompilation errors in some CFGs with try/catch statements
This commit is contained in:
Alexey Andreev 2015-10-15 11:03:06 +03:00
parent 24952e5b86
commit cf92616a6a
19 changed files with 278 additions and 71 deletions

View File

@ -348,7 +348,7 @@ public class Decompiler {
private AsyncMethodPart getRegularMethodStatement(Program program, int[] targetBlocks, boolean async) { private AsyncMethodPart getRegularMethodStatement(Program program, int[] targetBlocks, boolean async) {
AsyncMethodPart result = new AsyncMethodPart(); AsyncMethodPart result = new AsyncMethodPart();
lastBlockId = 1; lastBlockId = 1;
graph = ProgramUtils.buildControlFlowGraphWithTryCatch(program); graph = ProgramUtils.buildControlFlowGraph(program);
int[] weights = new int[graph.size()]; int[] weights = new int[graph.size()];
for (int i = 0; i < weights.length; ++i) { for (int i = 0; i < weights.length; ++i) {
weights[i] = program.basicBlockAt(i).getInstructions().size(); weights[i] = program.basicBlockAt(i).getInstructions().size();
@ -391,7 +391,7 @@ public class Decompiler {
generator.nextBlock = tmp >= 0 && next < indexer.size() ? program.basicBlockAt(tmp) : null; generator.nextBlock = tmp >= 0 && next < indexer.size() ? program.basicBlockAt(tmp) : null;
} }
closeExpiredBookmarks(generator, generator.currentBlock.getTryCatchBlocks()); closeExpiredBookmarks(generator, node, generator.currentBlock.getTryCatchBlocks());
List<TryCatchBookmark> inheritedBookmarks = new ArrayList<>(); List<TryCatchBookmark> inheritedBookmarks = new ArrayList<>();
Block block = stack.peek(); Block block = stack.peek();
@ -475,7 +475,7 @@ public class Decompiler {
return result; return result;
} }
private void closeExpiredBookmarks(StatementGenerator generator, List<TryCatchBlock> tryCatchBlocks) { private void closeExpiredBookmarks(StatementGenerator generator, int node, List<TryCatchBlock> tryCatchBlocks) {
tryCatchBlocks = new ArrayList<>(tryCatchBlocks); tryCatchBlocks = new ArrayList<>(tryCatchBlocks);
Collections.reverse(tryCatchBlocks); Collections.reverse(tryCatchBlocks);
@ -514,8 +514,10 @@ public class Decompiler {
TryCatchStatement tryCatchStmt = new TryCatchStatement(); TryCatchStatement tryCatchStmt = new TryCatchStatement();
tryCatchStmt.setExceptionType(bookmark.exceptionType); tryCatchStmt.setExceptionType(bookmark.exceptionType);
tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable); tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable);
if (node != bookmark.exceptionHandler) {
tryCatchStmt.getHandler().add(generator.generateJumpStatement( tryCatchStmt.getHandler().add(generator.generateJumpStatement(
program.basicBlockAt(bookmark.exceptionHandler))); program.basicBlockAt(bookmark.exceptionHandler)));
}
List<Statement> blockPart = block.body.subList(bookmark.offset, block.body.size()); List<Statement> blockPart = block.body.subList(bookmark.offset, block.body.size());
tryCatchStmt.getProtectedBody().addAll(blockPart); tryCatchStmt.getProtectedBody().addAll(blockPart);
blockPart.clear(); blockPart.clear();

View File

@ -151,7 +151,7 @@ public class AsyncProgramSplitter {
IntegerArray splitPoints = IntegerArray.of(part.splitPoints); IntegerArray splitPoints = IntegerArray.of(part.splitPoints);
AsyncProgramSplittingBackend splittingBackend = new AsyncProgramSplittingBackend( AsyncProgramSplittingBackend splittingBackend = new AsyncProgramSplittingBackend(
new ProgramNodeSplittingBackend(part.program), blockSuccessors, originalBlocks, splitPoints); new ProgramNodeSplittingBackend(part.program), blockSuccessors, originalBlocks, splitPoints);
Graph graph = ProgramUtils.buildControlFlowGraphWithTryCatch(part.program); Graph graph = ProgramUtils.buildControlFlowGraph(part.program);
int[] weights = new int[graph.size()]; int[] weights = new int[graph.size()];
for (int i = 0; i < part.program.basicBlockCount(); ++i) { for (int i = 0; i < part.program.basicBlockCount(); ++i) {
weights[i] = part.program.basicBlockAt(i).getInstructions().size(); weights[i] = part.program.basicBlockAt(i).getInstructions().size();

View File

@ -56,4 +56,9 @@ public class JUnitTestAdapter implements TestAdapter {
} }
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public Class<? extends TestRunner> getRunner(MethodReader method) {
return SimpleTestRunner.class;
}
} }

View File

@ -0,0 +1,27 @@
/*
* 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.testing;
/**
*
* @author Alexey Andreev
*/
public class SimpleTestRunner implements TestRunner {
@Override
public void run(TestLauncher launcher) throws Throwable {
launcher.launch();
}
}

View File

@ -27,4 +27,6 @@ public interface TestAdapter {
boolean acceptMethod(MethodReader method); boolean acceptMethod(MethodReader method);
Iterable<String> getExpectedExceptions(MethodReader method); Iterable<String> getExpectedExceptions(MethodReader method);
Class<? extends TestRunner> getRunner(MethodReader method);
} }

View File

@ -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.testing;
/**
*
* @author Alexey Andreev
*/
public interface TestLauncher {
void launch() throws Throwable;
}

View File

@ -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.testing;
/**
*
* @author Alexey Andreev
*/
public interface TestRunner {
void run(TestLauncher launcher) throws Throwable;
}

View File

@ -21,6 +21,7 @@ import java.util.Collections;
import org.netbeans.html.json.tck.KOTest; import org.netbeans.html.json.tck.KOTest;
import org.teavm.model.MethodReader; import org.teavm.model.MethodReader;
import org.teavm.testing.TestAdapter; import org.teavm.testing.TestAdapter;
import org.teavm.testing.TestRunner;
/** /**
* *
@ -48,4 +49,9 @@ public class KOTestAdapter implements TestAdapter {
public Iterable<String> getExpectedExceptions(MethodReader method) { public Iterable<String> getExpectedExceptions(MethodReader method) {
return Collections.emptyList(); return Collections.emptyList();
} }
@Override
public Class<? extends TestRunner> getRunner(MethodReader method) {
return KOTestRunner.class;
}
} }

View File

@ -0,0 +1,42 @@
/*
* 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.html4j.testing;
import org.teavm.testing.TestLauncher;
import org.teavm.testing.TestRunner;
/**
*
* @author Alexey Andreev
*/
public class KOTestRunner implements TestRunner {
@Override
public void run(TestLauncher launcher) throws Throwable {
int repeatCount = 0;
while (true) {
try {
launcher.launch();
break;
} catch (InterruptedException e) {
if (++repeatCount == 10) {
throw e;
}
} catch (Throwable e) {
throw e;
}
}
}
}

View File

@ -44,7 +44,6 @@ import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference; import org.teavm.model.MethodReference;
import org.teavm.model.PreOptimizingClassHolderSource; import org.teavm.model.PreOptimizingClassHolderSource;
import org.teavm.model.ProgramCache; import org.teavm.model.ProgramCache;
import org.teavm.model.ValueType;
import org.teavm.parsing.ClasspathClassHolderSource; import org.teavm.parsing.ClasspathClassHolderSource;
import org.teavm.testing.JUnitTestAdapter; import org.teavm.testing.JUnitTestAdapter;
import org.teavm.testing.TestAdapter; import org.teavm.testing.TestAdapter;
@ -362,12 +361,14 @@ public class TeaVMTestTool implements BaseTeaVMTool {
exceptions.add(exception); exceptions.add(exception);
} }
TestMethodBuilder testMethod = new TestMethodBuilder(ref, fileName, exceptions); String runner = adapter.getRunner(method).getName();
TestMethodBuilder testMethod = new TestMethodBuilder(ref, fileName, exceptions, runner);
testClass.getMethods().add(testMethod); testClass.getMethods().add(testMethod);
String debugTable = debugInformationGenerated ? testMethod.getFileName() + ".teavmdbg" : null; String debugTable = debugInformationGenerated ? testMethod.getFileName() + ".teavmdbg" : null;
cases.add(new TestCase(ref.toString(), testMethod.getFileName(), debugTable, cases.add(new TestCase(ref.toString(), testMethod.getFileName(), debugTable,
testMethod.getExpectedExceptions())); testMethod.getExpectedExceptions(), runner));
++testCount; ++testCount;
} }
} }
@ -417,6 +418,7 @@ public class TeaVMTestTool implements BaseTeaVMTool {
vm.setMinifying(minifying); vm.setMinifying(minifying);
vm.installPlugins(); vm.installPlugins();
new TestExceptionPlugin().install(vm); new TestExceptionPlugin().install(vm);
new TestEntryPointTransformer(testMethod.getRunner(), testMethod.getMethod()).install(vm);
for (ClassHolderTransformer transformer : transformers) { for (ClassHolderTransformer transformer : transformers) {
vm.add(transformer); vm.add(transformer);
} }
@ -426,13 +428,10 @@ public class TeaVMTestTool implements BaseTeaVMTool {
? new DebugInformationBuilder() : null; ? new DebugInformationBuilder() : null;
MethodReference methodRef = testMethod.getMethod(); MethodReference methodRef = testMethod.getMethod();
try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) {
MethodReference cons = new MethodReference(methodRef.getClassName(), "<init>", ValueType.VOID);
MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException", MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException",
Throwable.class, String.class); Throwable.class, String.class);
vm.entryPoint("initInstance", cons); vm.entryPoint("runTest", new MethodReference(TestEntryPoint.class, "run", void.class)).async();
vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()).async();
vm.entryPoint("extractException", exceptionMsg); vm.entryPoint("extractException", exceptionMsg);
vm.exportType("TestClass", cons.getClassName());
vm.setDebugEmitter(debugInfoBuilder); vm.setDebugEmitter(debugInfoBuilder);
vm.build(innerWriter, new DirectoryBuildTarget(outputDir)); vm.build(innerWriter, new DirectoryBuildTarget(outputDir));
innerWriter.append("\n"); innerWriter.append("\n");

View File

@ -31,17 +31,20 @@ public class TestCase {
private String testScript; private String testScript;
private String debugTable; private String debugTable;
private List<String> expectedExceptions = new ArrayList<>(); private List<String> expectedExceptions = new ArrayList<>();
private String runner;
@JsonCreator @JsonCreator
public TestCase( public TestCase(
@JsonProperty("testMethod") String testMethod, @JsonProperty("testMethod") String testMethod,
@JsonProperty("script") String testScript, @JsonProperty("script") String testScript,
@JsonProperty("debugTable") String debugTable, @JsonProperty("debugTable") String debugTable,
@JsonProperty("expectedExceptions") List<String> expectedExceptions) { @JsonProperty("expectedExceptions") List<String> expectedExceptions,
@JsonProperty("runner") String runner) {
this.testMethod = testMethod; this.testMethod = testMethod;
this.testScript = testScript; this.testScript = testScript;
this.debugTable = debugTable; this.debugTable = debugTable;
this.expectedExceptions = Collections.unmodifiableList(new ArrayList<>(expectedExceptions)); this.expectedExceptions = Collections.unmodifiableList(new ArrayList<>(expectedExceptions));
this.runner = runner;
} }
@JsonGetter @JsonGetter
@ -63,4 +66,9 @@ public class TestCase {
public List<String> getExpectedExceptions() { public List<String> getExpectedExceptions() {
return expectedExceptions; return expectedExceptions;
} }
@JsonGetter
public String getRunner() {
return runner;
}
} }

View File

@ -0,0 +1,35 @@
/*
* 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.testing;
import org.teavm.testing.TestRunner;
/**
*
* @author Alexey Andreev
*/
final class TestEntryPoint {
private TestEntryPoint() {
}
public static void run() throws Throwable {
createRunner().run(() -> launchTest());
}
private static native TestRunner createRunner();
private static native void launchTest();
}

View File

@ -0,0 +1,77 @@
/*
* 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.testing;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.emit.ProgramEmitter;
import org.teavm.vm.spi.TeaVMHost;
import org.teavm.vm.spi.TeaVMPlugin;
/**
*
* @author Alexey Andreev
*/
class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
private String runnerClassName;
private MethodReference testMethod;
public TestEntryPointTransformer(String runnerClassName, MethodReference testMethod) {
this.runnerClassName = runnerClassName;
this.testMethod = testMethod;
}
@Override
public void install(TeaVMHost host) {
host.add(this);
}
@Override
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
if (cls.getName().equals(TestEntryPoint.class.getName())) {
for (MethodHolder method : cls.getMethods()) {
if (method.equals(method)) {
if (method.getName().equals("createRunner")) {
method.setProgram(generateRunnerProgram(method, innerSource));
method.getModifiers().remove(ElementModifier.NATIVE);
} else if (method.getName().equals("launchTest")) {
method.setProgram(generateLaunchProgram(method, innerSource));
method.getModifiers().remove(ElementModifier.NATIVE);
}
}
}
}
}
private Program generateRunnerProgram(MethodHolder method, ClassReaderSource innerSource) {
ProgramEmitter pe = ProgramEmitter.create(method, innerSource);
pe.construct(runnerClassName).returnValue();
return pe.getProgram();
}
private Program generateLaunchProgram(MethodHolder method, ClassReaderSource innerSource) {
ProgramEmitter pe = ProgramEmitter.create(method, innerSource);
pe.construct(testMethod.getClassName()).invokeSpecial(testMethod);
pe.exit();
return pe.getProgram();
}
}

View File

@ -28,11 +28,14 @@ class TestMethodBuilder {
private MethodReference method; private MethodReference method;
private String fileName; private String fileName;
private List<String> expectedExceptions = new ArrayList<>(); private List<String> expectedExceptions = new ArrayList<>();
private String runner;
public TestMethodBuilder(MethodReference method, String fileName, List<String> expectedExceptions) { public TestMethodBuilder(MethodReference method, String fileName, List<String> expectedExceptions,
String runner) {
this.method = method; this.method = method;
this.fileName = fileName; this.fileName = fileName;
this.expectedExceptions = Collections.unmodifiableList(new ArrayList<>(expectedExceptions)); this.expectedExceptions = Collections.unmodifiableList(new ArrayList<>(expectedExceptions));
this.runner = runner;
} }
public MethodReference getMethod() { public MethodReference getMethod() {
@ -46,4 +49,8 @@ class TestMethodBuilder {
public List<String> getExpectedExceptions() { public List<String> getExpectedExceptions() {
return expectedExceptions; return expectedExceptions;
} }
public String getRunner() {
return runner;
}
} }

View File

@ -25,25 +25,8 @@ JUnitClient.runTest = function() {
} }
loop: while (true) { switch (ptr) { loop: while (true) { switch (ptr) {
case 0: case 0:
instance = new TestClass();
ptr = 1;
case 1:
try { try {
initInstance(instance); runTest();
} 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) { } catch (e) {
message = {}; message = {};
JUnitClient.makeErrorMessage(message, e); JUnitClient.makeErrorMessage(message, e);

View File

@ -6,7 +6,7 @@ Bundle-Version: 0.4.0.qualifier
Bundle-Vendor: Alexey Andreev <konsoletyper@gmail.com> Bundle-Vendor: Alexey Andreev <konsoletyper@gmail.com>
Bundle-RequiredExecutionEnvironment: JavaSE-1.7 Bundle-RequiredExecutionEnvironment: JavaSE-1.7
Bundle-ClassPath: ., Bundle-ClassPath: .,
lib/asm-debug-all-5.0.3.jar, lib/asm-debug-all-5.0.4.jar,
lib/cdi-api-1.2.jar, lib/cdi-api-1.2.jar,
lib/commons-io-2.4.jar, lib/commons-io-2.4.jar,
lib/jackson-core-asl-1.9.13.jar, lib/jackson-core-asl-1.9.13.jar,

View File

@ -3,7 +3,7 @@ output.. = target/
bin.includes = META-INF/,\ bin.includes = META-INF/,\
.,\ .,\
lib/,\ lib/,\
lib/asm-debug-all-5.0.3.jar,\ lib/asm-debug-all-5.0.4.jar,\
lib/cdi-api-1.2.jar,\ lib/cdi-api-1.2.jar,\
lib/commons-io-2.4.jar,\ lib/commons-io-2.4.jar,\
lib/jackson-core-asl-1.9.13.jar,\ lib/jackson-core-asl-1.9.13.jar,\

View File

@ -12,25 +12,8 @@ function(callback) {
} }
loop: while (true) { switch (ptr) { loop: while (true) { switch (ptr) {
case 0: case 0:
instance = new TestClass();
ptr = 1;
case 1:
try { try {
initInstance(instance); runTest();
} 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) { } catch (e) {
message = {}; message = {};
JUnitClient.makeErrorMessage(message, e); JUnitClient.makeErrorMessage(message, e);

View File

@ -11,25 +11,8 @@ JUnitClient.run = function() {
} }
loop: while (true) { switch (ptr) { loop: while (true) { switch (ptr) {
case 0: case 0:
instance = new TestClass();
ptr = 1;
case 1:
try { try {
initInstance(instance); runTest();
} 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) { } catch (e) {
message = {}; message = {};
JUnitClient.makeErrorMessage(message, e); JUnitClient.makeErrorMessage(message, e);