Support running JUnit3 tests and improve support of JUnit4

This commit is contained in:
Alexey Andreev 2018-11-22 19:51:33 +03:00
parent cd7a702c31
commit 6d2815bc5c
16 changed files with 384 additions and 286 deletions

View File

@ -15,7 +15,6 @@
<option value="-Dteavm.build.all=false" /> <option value="-Dteavm.build.all=false" />
<option value="-Pwith-cli" /> <option value="-Pwith-cli" />
<option value="-Dmaven.javadoc.skip=true" /> <option value="-Dmaven.javadoc.skip=true" />
<option value="-Dmaven.source.skip=true" />
</list> </list>
</option> </option>
<option name="pomFileName" /> <option name="pomFileName" />

View File

@ -31,7 +31,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>provided</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.teavm</groupId> <groupId>org.teavm</groupId>

View File

@ -1,60 +0,0 @@
/*
* 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.testing;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import org.junit.Test;
import org.teavm.model.AnnotationReader;
import org.teavm.model.AnnotationValue;
import org.teavm.model.MethodReader;
import org.teavm.model.ValueType;
public class JUnitTestAdapter implements TestAdapter {
@Override
public boolean acceptClass(Class<?> cls) {
for (Method method : cls.getDeclaredMethods()) {
for (Annotation annot : method.getAnnotations()) {
if (annot.annotationType().getName().equals(Test.class.getName())) {
return true;
}
}
}
return false;
}
@Override
public boolean acceptMethod(MethodReader method) {
return method.getAnnotations().get(Test.class.getName()) != null;
}
@Override
public Iterable<String> getExpectedExceptions(MethodReader method) {
AnnotationReader annot = method.getAnnotations().get(Test.class.getName());
AnnotationValue expectedAnnot = annot.getValue("expected");
if (expectedAnnot != null) {
String className = ((ValueType.Object) expectedAnnot.getJavaClass()).getClassName();
return Collections.singletonList(className);
}
return Collections.emptyList();
}
@Override
public Class<? extends TestRunner> getRunner(MethodReader method) {
return SimpleTestRunner.class;
}
}

View File

@ -1,20 +0,0 @@
/*
* 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;
public interface TestRunner {
void run(TestLauncher launcher) throws Throwable;
}

View File

@ -17,11 +17,12 @@ package org.teavm.vm;
import org.teavm.interop.PlatformMarker; import org.teavm.interop.PlatformMarker;
import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderSource;
import org.teavm.model.ClassReaderSource;
import org.teavm.parsing.ClasspathClassHolderSource; import org.teavm.parsing.ClasspathClassHolderSource;
public class TeaVMBuilder { public class TeaVMBuilder {
TeaVMTarget target; TeaVMTarget target;
ClassHolderSource classSource; ClassReaderSource classSource;
ClassLoader classLoader; ClassLoader classLoader;
public TeaVMBuilder(TeaVMTarget target) { public TeaVMBuilder(TeaVMTarget target) {
@ -30,7 +31,7 @@ public class TeaVMBuilder {
classSource = !isBootstrap() ? new ClasspathClassHolderSource(classLoader) : name -> null; classSource = !isBootstrap() ? new ClasspathClassHolderSource(classLoader) : name -> null;
} }
public ClassHolderSource getClassSource() { public ClassReaderSource getClassSource() {
return classSource; return classSource;
} }

View File

@ -40,12 +40,6 @@
<dependency> <dependency>
<groupId>org.netbeans.html</groupId> <groupId>org.netbeans.html</groupId>
<artifactId>net.java.html.boot</artifactId> <artifactId>net.java.html.boot</artifactId>
<exclusions>
<exclusion>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.netbeans.html</groupId> <groupId>org.netbeans.html</groupId>

View File

@ -1,54 +0,0 @@
/*
* 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.html4j.testing;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Collections;
import org.teavm.model.MethodReader;
import org.teavm.testing.TestAdapter;
import org.teavm.testing.TestRunner;
public class KOTestAdapter implements TestAdapter {
static final String KO_TEST_CLASS = "org.netbeans.html.json.tck.KOTest";
@Override
public boolean acceptClass(Class<?> cls) {
for (Method method : cls.getDeclaredMethods()) {
for (Annotation annot : method.getAnnotations()) {
if (annot.annotationType().getName().equals(KO_TEST_CLASS)) {
return true;
}
}
}
return false;
}
@Override
public boolean acceptMethod(MethodReader method) {
return method.getAnnotations().get(KO_TEST_CLASS) != null;
}
@Override
public Iterable<String> getExpectedExceptions(MethodReader method) {
return Collections.emptyList();
}
@Override
public Class<? extends TestRunner> getRunner(MethodReader method) {
return KOTestRunner.class;
}
}

View File

@ -1,37 +0,0 @@
/*
* 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;
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;
}
Thread.sleep(50);
}
}
}
}

View File

@ -83,6 +83,21 @@
<version>0.7.3</version> <version>0.7.3</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mozilla</groupId>
<artifactId>rhino</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Alexey Andreev. * Copyright 2018 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,11 +13,21 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.testing; package org.teavm.tests;
import junit.framework.TestCase;
public abstract class JUnit3BaseTest extends TestCase {
String a;
String b;
public class SimpleTestRunner implements TestRunner {
@Override @Override
public void run(TestLauncher launcher) throws Throwable { protected void setUp() throws Exception {
launcher.launch(); super.setUp();
a = "start";
}
public void testFoo() {
assertEquals("start", a);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2014 Alexey Andreev. * Copyright 2018 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,16 +13,20 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.testing; package org.teavm.tests;
import org.teavm.model.MethodReader; import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMTestRunner;
public interface TestAdapter { @RunWith(TeaVMTestRunner.class)
boolean acceptClass(Class<?> cls); public class JUnit3DerivedTest extends JUnit3BaseTest {
@Override
protected void setUp() throws Exception {
super.setUp();
b = "derived";
}
boolean acceptMethod(MethodReader method); public void testBar() {
assertEquals("derived", b);
Iterable<String> getExpectedExceptions(MethodReader method); }
Class<? extends TestRunner> getRunner(MethodReader method);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Jaroslav Tulach. * Copyright 2018 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,17 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.html4j.testing; package org.teavm.tests;
import static org.junit.Assert.assertEquals;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
/** public abstract class JUnitBaseTest {
* String a;
* @author Jaroslav Tulach String b;
*/
public class KOTestAdapterTest { @Before
public void startBase() {
a = "start";
}
@Test @Test
public void verifyKOTestClassNameIsCorrect() throws ClassNotFoundException { public void foo() {
Class.forName(KOTestAdapter.KO_TEST_CLASS); assertEquals("start", a);
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2015 Alexey Andreev. * Copyright 2018 Alexey Andreev.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -13,8 +13,23 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.teavm.testing; package org.teavm.tests;
public interface TestLauncher { import static org.junit.Assert.assertEquals;
void launch() throws Throwable; import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
public class JUnitDerivedTest extends JUnitBaseTest {
@Before
public void startDerived() {
b = "derived";
}
@Test
public void bar() {
assertEquals("derived", b);
}
} }

View File

@ -15,17 +15,21 @@
*/ */
package org.teavm.junit; package org.teavm.junit;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -41,7 +45,12 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import junit.framework.TestCase;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.Description; import org.junit.runner.Description;
import org.junit.runner.Runner; import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter; import org.junit.runner.manipulation.Filter;
@ -54,17 +63,20 @@ import org.teavm.backend.c.CTarget;
import org.teavm.backend.javascript.JavaScriptTarget; import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.backend.wasm.WasmTarget; import org.teavm.backend.wasm.WasmTarget;
import org.teavm.callgraph.CallGraph; import org.teavm.callgraph.CallGraph;
import org.teavm.debugging.information.DebugInformation;
import org.teavm.debugging.information.DebugInformationBuilder;
import org.teavm.diagnostics.DefaultProblemTextConsumer; import org.teavm.diagnostics.DefaultProblemTextConsumer;
import org.teavm.diagnostics.Problem; import org.teavm.diagnostics.Problem;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.AnnotationValue;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource; import org.teavm.model.ClassHolderSource;
import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.PreOptimizingClassHolderSource; import org.teavm.model.PreOptimizingClassHolderSource;
import org.teavm.model.ValueType; import org.teavm.model.ValueType;
import org.teavm.parsing.ClasspathClassHolderSource; import org.teavm.parsing.ClasspathClassHolderSource;
import org.teavm.testing.JUnitTestAdapter;
import org.teavm.testing.TestAdapter;
import org.teavm.tooling.TeaVMProblemRenderer; import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.vm.DirectoryBuildTarget; import org.teavm.vm.DirectoryBuildTarget;
import org.teavm.vm.TeaVM; import org.teavm.vm.TeaVM;
@ -72,6 +84,12 @@ import org.teavm.vm.TeaVMBuilder;
import org.teavm.vm.TeaVMTarget; import org.teavm.vm.TeaVMTarget;
public class TeaVMTestRunner extends Runner implements Filterable { public class TeaVMTestRunner extends Runner implements Filterable {
static final String JUNIT3_BASE_CLASS = "junit.framework.TestCase";
static final MethodReference JUNIT3_BEFORE = new MethodReference(JUNIT3_BASE_CLASS, "setUp", ValueType.VOID);
static final MethodReference JUNIT3_AFTER = new MethodReference(JUNIT3_BASE_CLASS, "tearDown", ValueType.VOID);
static final String JUNIT4_TEST = "org.junit.Test";
static final String JUNIT4_BEFORE = "org.junit.Before";
static final String JUNIT4_AFTER = "org.junit.After";
private static final String PATH_PARAM = "teavm.junit.target"; private static final String PATH_PARAM = "teavm.junit.target";
private static final String JS_RUNNER = "teavm.junit.js.runner"; private static final String JS_RUNNER = "teavm.junit.js.runner";
private static final String THREAD_COUNT = "teavm.junit.js.threads"; private static final String THREAD_COUNT = "teavm.junit.js.threads";
@ -85,12 +103,11 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private static final int stopTimeout = 15000; private static final int stopTimeout = 15000;
private Class<?> testClass; private Class<?> testClass;
private ClassHolder classHolder; private ClassHolderSource classSource;
private ClassLoader classLoader; private ClassLoader classLoader;
private Description suiteDescription; private Description suiteDescription;
private static Map<ClassLoader, ClassHolderSource> classSources = new WeakHashMap<>(); private static Map<ClassLoader, ClassHolderSource> classSources = new WeakHashMap<>();
private File outputDir; private File outputDir;
private TestAdapter testAdapter = new JUnitTestAdapter();
private Map<Method, Description> descriptions = new HashMap<>(); private Map<Method, Description> descriptions = new HashMap<>();
private static Map<RunKind, RunnerKindInfo> runners = new HashMap<>(); private static Map<RunKind, RunnerKindInfo> runners = new HashMap<>();
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1); private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
@ -124,8 +141,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
public TeaVMTestRunner(Class<?> testClass) throws InitializationError { public TeaVMTestRunner(Class<?> testClass) throws InitializationError {
this.testClass = testClass; this.testClass = testClass;
classLoader = TeaVMTestRunner.class.getClassLoader(); classLoader = TeaVMTestRunner.class.getClassLoader();
ClassHolderSource classSource = getClassSource(classLoader); classSource = getClassSource(classLoader);
classHolder = classSource.get(testClass.getName());
String outputPath = System.getProperty(PATH_PARAM); String outputPath = System.getProperty(PATH_PARAM);
if (outputPath != null) { if (outputPath != null) {
outputDir = new File(outputPath); outputDir = new File(outputPath);
@ -166,7 +182,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (suiteDescription == null) { if (suiteDescription == null) {
suiteDescription = Description.createSuiteDescription(testClass); suiteDescription = Description.createSuiteDescription(testClass);
for (Method child : getFilteredChildren()) { for (Method child : getFilteredChildren()) {
suiteDescription.getChildren().add(describeChild(child)); suiteDescription.addChild(describeChild(child));
} }
} }
return suiteDescription; return suiteDescription;
@ -196,15 +212,29 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private List<Method> getChildren() { private List<Method> getChildren() {
List<Method> children = new ArrayList<>(); List<Method> children = new ArrayList<>();
for (Method method : testClass.getDeclaredMethods()) { Class<?> cls = testClass;
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method)); Set<String> foundMethods = new HashSet<>();
if (testAdapter.acceptMethod(methodHolder)) { while (cls != Object.class && !cls.getName().equals(JUNIT3_BASE_CLASS)) {
children.add(method); for (Method method : cls.getDeclaredMethods()) {
if (foundMethods.add(method.getName()) && isTestMethod(method)) {
children.add(method);
}
} }
cls = cls.getSuperclass();
} }
return children; return children;
} }
private boolean isTestMethod(Method method) {
if (TestCase.class.isAssignableFrom(method.getDeclaringClass())) {
return method.getName().startsWith("test") && method.getName().length() > 4
&& Character.isUpperCase(method.getName().charAt(4));
} else {
return method.isAnnotationPresent(Test.class);
}
}
private List<Method> getFilteredChildren() { private List<Method> getFilteredChildren() {
if (filteredChildren == null) { if (filteredChildren == null) {
filteredChildren = getChildren(); filteredChildren = getChildren();
@ -218,31 +248,39 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
private void runChild(Method child, RunNotifier notifier) { private void runChild(Method child, RunNotifier notifier) {
notifier.fireTestStarted(describeChild(child)); Description description = describeChild(child);
notifier.fireTestStarted(description);
if (child.isAnnotationPresent(Ignore.class)) {
notifier.fireTestIgnored(description);
latch.countDown();
return;
}
boolean ran = false; boolean ran = false;
boolean success = true; boolean success = true;
ClassHolder classHolder = classSource.get(child.getDeclaringClass().getName());
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(child)); MethodHolder methodHolder = classHolder.getMethod(getDescriptor(child));
Set<Class<?>> expectedExceptions = new HashSet<>(); Set<Class<?>> expectedExceptions = new HashSet<>();
for (String exceptionName : testAdapter.getExpectedExceptions(methodHolder)) { for (String exceptionName : getExpectedExceptions(methodHolder)) {
try { try {
expectedExceptions.add(Class.forName(exceptionName, false, classLoader)); expectedExceptions.add(Class.forName(exceptionName, false, classLoader));
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
notifier.fireTestFailure(new Failure(describeChild(child), e)); notifier.fireTestFailure(new Failure(description, e));
notifier.fireTestFinished(describeChild(child)); notifier.fireTestFinished(description);
latch.countDown(); latch.countDown();
return; return;
} }
} }
if (!child.isAnnotationPresent(SkipJVM.class) if (!child.isAnnotationPresent(SkipJVM.class)
&& !child.getDeclaringClass().isAnnotationPresent(SkipJVM.class)) { && !testClass.isAnnotationPresent(SkipJVM.class)) {
ran = true; ran = true;
success = runInJvm(child, notifier, expectedExceptions); success = runInJvm(child, notifier, expectedExceptions);
} }
Description description = describeChild(child);
if (success && outputDir != null) { if (success && outputDir != null) {
int[] configurationIndex = new int[] { 0 }; int[] configurationIndex = new int[] { 0 };
List<Consumer<Boolean>> onSuccess = new ArrayList<>(); List<Consumer<Boolean>> onSuccess = new ArrayList<>();
@ -302,42 +340,136 @@ public class TeaVMTestRunner extends Runner implements Filterable {
} }
} }
private String[] getExpectedExceptions(MethodHolder method) {
AnnotationHolder annot = method.getAnnotations().get(JUNIT4_TEST);
if (annot == null) {
return new String[0];
}
AnnotationValue expected = annot.getValue("expected");
if (expected == null) {
return new String[0];
}
ValueType result = expected.getJavaClass();
return new String[] { ((ValueType.Object) result).getClassName() };
}
private boolean runInJvm(Method child, RunNotifier notifier, Set<Class<?>> expectedExceptions) { private boolean runInJvm(Method child, RunNotifier notifier, Set<Class<?>> expectedExceptions) {
Description description = describeChild(child);
Runner runner;
Object instance; Object instance;
try { try {
instance = testClass.newInstance(); instance = testClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) { } catch (InstantiationException | IllegalAccessException e) {
notifier.fireTestFailure(new Failure(describeChild(child), e)); notifier.fireTestFailure(new Failure(description, e));
return false; return false;
} }
if (!TestCase.class.isAssignableFrom(testClass)) {
runner = new JUnit4Runner(instance, child);
} else {
runner = new JUnit3Runner(instance);
((TestCase) instance).setName(child.getName());
}
boolean expectedCaught = false; List<Class<?>> classes = new ArrayList<>();
try { Class<?> cls = instance.getClass();
child.invoke(instance); while (cls != null) {
} catch (IllegalAccessException e) { classes.add(cls);
notifier.fireTestFailure(new Failure(describeChild(child), e)); cls = cls.getSuperclass();
return false; }
} catch (InvocationTargetException e) { Collections.reverse(classes);
boolean wasExpected = false; for (Class<?> c : classes) {
for (Class<?> expected : expectedExceptions) { for (Method method : c.getMethods()) {
if (expected.isInstance(e.getTargetException())) { if (method.isAnnotationPresent(Before.class)) {
expectedCaught = true; try {
wasExpected = true; method.invoke(instance);
} catch (InvocationTargetException e) {
notifier.fireTestFailure(new Failure(description, e.getTargetException()));
} catch (IllegalAccessException e) {
notifier.fireTestFailure(new Failure(description, e));
}
} }
} }
if (!wasExpected) { }
notifier.fireTestFailure(new Failure(describeChild(child), e.getTargetException()));
try {
boolean expectedCaught = false;
try {
runner.run();
} catch (Throwable e) {
boolean wasExpected = false;
for (Class<?> expected : expectedExceptions) {
if (expected.isInstance(e)) {
expectedCaught = true;
wasExpected = true;
}
}
if (!wasExpected) {
notifier.fireTestFailure(new Failure(description, e));
return false;
}
return false; return false;
} }
if (!expectedCaught && !expectedExceptions.isEmpty()) {
notifier.fireTestAssumptionFailed(new Failure(description,
new AssertionError("Expected exception was not thrown")));
return false;
}
return true;
} finally {
Collections.reverse(classes);
for (Class<?> c : classes) {
for (Method method : c.getMethods()) {
if (method.isAnnotationPresent(After.class)) {
try {
method.invoke(instance);
} catch (InvocationTargetException e) {
notifier.fireTestFailure(new Failure(description, e.getTargetException()));
} catch (IllegalAccessException e) {
notifier.fireTestFailure(new Failure(description, e));
}
}
}
}
}
}
interface Runner {
void run() throws Throwable;
}
class JUnit4Runner implements Runner {
Object instance;
Method child;
JUnit4Runner(Object instance, Method child) {
this.instance = instance;
this.child = child;
} }
if (!expectedCaught && !expectedExceptions.isEmpty()) { @Override
notifier.fireTestAssumptionFailed(new Failure(describeChild(child), public void run() throws Throwable {
new AssertionError("Expected exception was not thrown"))); try {
return false; child.invoke(instance);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
}
class JUnit3Runner implements Runner {
Object instance;
JUnit3Runner(Object instance) {
this.instance = instance;
} }
return true; @Override
public void run() throws Throwable {
((TestCase) instance).runBare();
}
} }
private TestRun compile(Method child, RunNotifier notifier, RunKind kind, private TestRun compile(Method child, RunNotifier notifier, RunKind kind,
@ -416,7 +548,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private File getOutputPath(Method method) { private File getOutputPath(Method method) {
File path = outputDir; File path = outputDir;
path = new File(path, method.getDeclaringClass().getName().replace('.', '/')); path = new File(path, testClass.getName().replace('.', '/'));
path = new File(path, method.getName()); path = new File(path, method.getName());
path.mkdirs(); path.mkdirs();
return path; return path;
@ -430,27 +562,46 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private CompileResult compileToJs(Method method, TeaVMTestConfiguration<JavaScriptTarget> configuration, private CompileResult compileToJs(Method method, TeaVMTestConfiguration<JavaScriptTarget> configuration,
File path) { File path) {
return compileTest(method, configuration, JavaScriptTarget::new, vm -> { DebugInformationBuilder debugEmitter = new DebugInformationBuilder();
vm.entryPoint(TestEntryPoint.class.getName()); Supplier<JavaScriptTarget> targetSupplier = () -> {
}, path, ".js"); JavaScriptTarget target = new JavaScriptTarget();
target.setDebugEmitter(debugEmitter);
return target;
};
CompilePostProcessor postBuild = (vm, file) -> {
DebugInformation debugInfo = debugEmitter.getDebugInformation();
File sourceMapsFile = new File(file.getPath() + ".map");
try {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file, true), UTF_8)) {
writer.write("\n//# sourceMappingURL=");
writer.write(sourceMapsFile.getName());
}
try (Writer sourceMapsOut = new OutputStreamWriter(new FileOutputStream(sourceMapsFile), UTF_8)) {
debugInfo.writeAsSourceMaps(sourceMapsOut, "src", file.getPath());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
};
return compileTest(method, configuration, targetSupplier, TestEntryPoint.class.getName(), path, ".js",
postBuild);
} }
private CompileResult compileToC(Method method, TeaVMTestConfiguration<CTarget> configuration, private CompileResult compileToC(Method method, TeaVMTestConfiguration<CTarget> configuration,
File path) { File path) {
return compileTest(method, configuration, CTarget::new, vm -> { return compileTest(method, configuration, CTarget::new, TestNativeEntryPoint.class.getName(), path, ".c", null);
vm.entryPoint(TestNativeEntryPoint.class.getName());
}, path, ".c");
} }
private CompileResult compileToWasm(Method method, TeaVMTestConfiguration<WasmTarget> configuration, private CompileResult compileToWasm(Method method, TeaVMTestConfiguration<WasmTarget> configuration,
File path) { File path) {
return compileTest(method, configuration, WasmTarget::new, vm -> { return compileTest(method, configuration, WasmTarget::new, TestNativeEntryPoint.class.getName(), path,
vm.entryPoint(TestNativeEntryPoint.class.getName()); ".wasm", null);
}, path, ".wasm");
} }
private <T extends TeaVMTarget> CompileResult compileTest(Method method, TeaVMTestConfiguration<T> configuration, private <T extends TeaVMTarget> CompileResult compileTest(Method method, TeaVMTestConfiguration<T> configuration,
Supplier<T> targetSupplier, Consumer<TeaVM> preBuild, File path, String extension) { Supplier<T> targetSupplier, String entryPoint, File path, String extension,
CompilePostProcessor postBuild) {
CompileResult result = new CompileResult(); CompileResult result = new CompileResult();
StringBuilder simpleName = new StringBuilder(); StringBuilder simpleName = new StringBuilder();
@ -464,10 +615,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
result.file = outputFile; result.file = outputFile;
ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader(); ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader();
ClassHolderSource classSource = getClassSource(classLoader);
ClassHolder classHolder = classSource.get(method.getDeclaringClass().getName());
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method)); MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
Class<?> runnerType = testAdapter.getRunner(methodHolder);
T target = targetSupplier.get(); T target = targetSupplier.get();
configuration.apply(target); configuration.apply(target);
@ -486,18 +636,26 @@ public class TeaVMTestRunner extends Runner implements Filterable {
vm.installPlugins(); vm.installPlugins();
new TestExceptionPlugin().install(vm); new TestExceptionPlugin().install(vm);
new TestEntryPointTransformer(runnerType.getName(), methodHolder.getReference()).install(vm); new TestEntryPointTransformer(methodHolder.getReference(), testClass.getName()).install(vm);
preBuild.accept(vm); vm.entryPoint(entryPoint);
vm.build(new DirectoryBuildTarget(outputFile.getParentFile()), outputFile.getName()); vm.build(new DirectoryBuildTarget(outputFile.getParentFile()), outputFile.getName());
if (!vm.getProblemProvider().getProblems().isEmpty()) { if (!vm.getProblemProvider().getProblems().isEmpty()) {
result.success = false; result.success = false;
result.errorMessage = buildErrorMessage(vm); result.errorMessage = buildErrorMessage(vm);
} else {
if (postBuild != null) {
postBuild.process(vm, outputFile);
}
} }
return result; return result;
} }
interface CompilePostProcessor {
void process(TeaVM vm, File targetFile);
}
private List<TeaVMTestConfiguration<JavaScriptTarget>> getJavaScriptConfigurations() { private List<TeaVMTestConfiguration<JavaScriptTarget>> getJavaScriptConfigurations() {
List<TeaVMTestConfiguration<JavaScriptTarget>> configurations = new ArrayList<>(); List<TeaVMTestConfiguration<JavaScriptTarget>> configurations = new ArrayList<>();
if (Boolean.parseBoolean(System.getProperty(JS_ENABLED, "true"))) { if (Boolean.parseBoolean(System.getProperty(JS_ENABLED, "true"))) {

View File

@ -15,8 +15,6 @@
*/ */
package org.teavm.junit; package org.teavm.junit;
import org.teavm.testing.TestRunner;
final class TestEntryPoint { final class TestEntryPoint {
private static Object testCase; private static Object testCase;
@ -24,14 +22,23 @@ final class TestEntryPoint {
} }
public static void run() throws Throwable { public static void run() throws Throwable {
createRunner().run(() -> launchTest()); before();
try {
launchTest();
} finally {
try {
after();
} catch (Throwable e) {
e.printStackTrace();
}
}
} }
private static native TestRunner createRunner(); private static native void before();
private static native void launchTest(); private static native void launchTest();
private static native boolean isExpectedException(Class<?> cls); private static native void after();
public static void main(String[] args) throws Throwable { public static void main(String[] args) throws Throwable {
run(); run();

View File

@ -15,13 +15,22 @@
*/ */
package org.teavm.junit; package org.teavm.junit;
import org.junit.Test; import static org.teavm.junit.TeaVMTestRunner.JUNIT3_AFTER;
import static org.teavm.junit.TeaVMTestRunner.JUNIT3_BASE_CLASS;
import static org.teavm.junit.TeaVMTestRunner.JUNIT3_BEFORE;
import static org.teavm.junit.TeaVMTestRunner.JUNIT4_AFTER;
import static org.teavm.junit.TeaVMTestRunner.JUNIT4_BEFORE;
import static org.teavm.junit.TeaVMTestRunner.JUNIT4_TEST;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.teavm.diagnostics.Diagnostics; import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.AnnotationReader; import org.teavm.model.AnnotationReader;
import org.teavm.model.AnnotationValue; import org.teavm.model.AnnotationValue;
import org.teavm.model.BasicBlock; import org.teavm.model.BasicBlock;
import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource; import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier; import org.teavm.model.ElementModifier;
import org.teavm.model.MethodHolder; import org.teavm.model.MethodHolder;
@ -36,12 +45,12 @@ import org.teavm.vm.spi.TeaVMHost;
import org.teavm.vm.spi.TeaVMPlugin; import org.teavm.vm.spi.TeaVMPlugin;
class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin { class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
private String runnerClassName;
private MethodReference testMethod; private MethodReference testMethod;
private String testClassName;
TestEntryPointTransformer(String runnerClassName, MethodReference testMethod) { TestEntryPointTransformer(MethodReference testMethod, String testClassName) {
this.runnerClassName = runnerClassName;
this.testMethod = testMethod; this.testMethod = testMethod;
this.testClassName = testClassName;
} }
@Override @Override
@ -53,38 +62,89 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) { public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
if (cls.getName().equals(TestEntryPoint.class.getName())) { if (cls.getName().equals(TestEntryPoint.class.getName())) {
for (MethodHolder method : cls.getMethods()) { for (MethodHolder method : cls.getMethods()) {
if (method.getName().equals("createRunner")) { switch (method.getName()) {
method.setProgram(generateRunnerProgram(method, innerSource)); case "launchTest":
method.getModifiers().remove(ElementModifier.NATIVE); method.setProgram(generateLaunchProgram(method, innerSource));
} else if (method.getName().equals("launchTest")) { method.getModifiers().remove(ElementModifier.NATIVE);
method.setProgram(generateLaunchProgram(method, innerSource)); break;
method.getModifiers().remove(ElementModifier.NATIVE); case "before":
method.setProgram(generateBeforeProgram(method, innerSource));
method.getModifiers().remove(ElementModifier.NATIVE);
break;
case "after":
method.setProgram(generateAfterProgram(method, innerSource));
method.getModifiers().remove(ElementModifier.NATIVE);
break;
} }
} }
} }
} }
private Program generateRunnerProgram(MethodHolder method, ClassReaderSource innerSource) { private Program generateBeforeProgram(MethodHolder method, ClassReaderSource innerSource) {
ProgramEmitter pe = ProgramEmitter.create(method, innerSource); ProgramEmitter pe = ProgramEmitter.create(method, innerSource);
pe.construct(runnerClassName).returnValue(); ValueEmitter testCaseInitVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
pe.when(testCaseInitVar.isNull())
.thenDo(() -> {
pe.setField(TestEntryPoint.class, "testCase",
pe.construct(testClassName).cast(Object.class));
});
ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
if (innerSource.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName()).orElse(false)) {
testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_BEFORE);
}
List<ClassReader> classes = collectSuperClasses(pe.getClassSource(), testMethod.getClassName());
Collections.reverse(classes);
classes.stream()
.flatMap(cls -> cls.getMethods().stream())
.filter(m -> m.getAnnotations().get(JUNIT4_BEFORE) != null)
.forEach(m -> testCaseVar.cast(ValueType.object(m.getOwnerName())).invokeVirtual(m.getReference()));
pe.exit();
return pe.getProgram(); return pe.getProgram();
} }
private Program generateAfterProgram(MethodHolder method, ClassReaderSource innerSource) {
ProgramEmitter pe = ProgramEmitter.create(method, innerSource);
ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
List<ClassReader> classes = collectSuperClasses(pe.getClassSource(), testMethod.getClassName());
classes.stream()
.flatMap(cls -> cls.getMethods().stream())
.filter(m -> m.getAnnotations().get(JUNIT4_AFTER) != null)
.forEach(m -> testCaseVar.cast(ValueType.object(m.getOwnerName())).invokeVirtual(m.getReference()));
if (innerSource.isSuperType(JUNIT3_BASE_CLASS, testMethod.getClassName()).orElse(false)) {
testCaseVar.cast(ValueType.object(JUNIT3_BASE_CLASS)).invokeVirtual(JUNIT3_AFTER);
}
pe.exit();
return pe.getProgram();
}
private List<ClassReader> collectSuperClasses(ClassReaderSource classSource, String className) {
List<ClassReader> result = new ArrayList<>();
while (className != null && !className.equals(JUNIT3_BASE_CLASS)) {
ClassReader cls = classSource.get(className);
if (cls == null) {
break;
}
result.add(cls);
className = cls.getParent();
}
return result;
}
private Program generateLaunchProgram(MethodHolder method, ClassReaderSource innerSource) { private Program generateLaunchProgram(MethodHolder method, ClassReaderSource innerSource) {
ProgramEmitter pe = ProgramEmitter.create(method, innerSource); ProgramEmitter pe = ProgramEmitter.create(method, innerSource);
ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
pe.when(testCaseVar.isNull())
.thenDo(() -> {
pe.setField(TestEntryPoint.class, "testCase",
pe.construct(testMethod.getClassName()).cast(Object.class));
});
pe.getField(TestEntryPoint.class, "testCase", Object.class) pe.getField(TestEntryPoint.class, "testCase", Object.class)
.cast(ValueType.object(testMethod.getClassName())) .cast(ValueType.object(testMethod.getClassName()))
.invokeSpecial(testMethod); .invokeSpecial(testMethod);
MethodReader testMethodReader = innerSource.resolve(testMethod); MethodReader testMethodReader = innerSource.resolve(testMethod);
AnnotationReader testAnnotation = testMethodReader.getAnnotations().get(Test.class.getName()); AnnotationReader testAnnotation = testMethodReader.getAnnotations().get(JUNIT4_TEST);
AnnotationValue throwsValue = testAnnotation.getValue("expected"); AnnotationValue throwsValue = testAnnotation != null ? testAnnotation.getValue("expected") : null;
if (throwsValue != null) { if (throwsValue != null) {
BasicBlock handler = pe.getProgram().createBasicBlock(); BasicBlock handler = pe.getProgram().createBasicBlock();
TryCatchBlock tryCatch = new TryCatchBlock(); TryCatchBlock tryCatch = new TryCatchBlock();