diff --git a/.idea/runConfigurations/build_teavm_fast.xml b/.idea/runConfigurations/build_teavm_fast.xml
index 5489ca572..f18528139 100644
--- a/.idea/runConfigurations/build_teavm_fast.xml
+++ b/.idea/runConfigurations/build_teavm_fast.xml
@@ -15,7 +15,6 @@
-
diff --git a/core/pom.xml b/core/pom.xml
index a5968d1d5..838bc758c 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -31,7 +31,7 @@
junit
junit
- provided
+ test
org.teavm
diff --git a/core/src/main/java/org/teavm/testing/JUnitTestAdapter.java b/core/src/main/java/org/teavm/testing/JUnitTestAdapter.java
deleted file mode 100644
index 5159cfbf0..000000000
--- a/core/src/main/java/org/teavm/testing/JUnitTestAdapter.java
+++ /dev/null
@@ -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 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;
- }
-}
diff --git a/core/src/main/java/org/teavm/testing/TestRunner.java b/core/src/main/java/org/teavm/testing/TestRunner.java
deleted file mode 100644
index c01a07be0..000000000
--- a/core/src/main/java/org/teavm/testing/TestRunner.java
+++ /dev/null
@@ -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;
-}
diff --git a/core/src/main/java/org/teavm/vm/TeaVMBuilder.java b/core/src/main/java/org/teavm/vm/TeaVMBuilder.java
index c6d52cd9d..576a5bdcf 100644
--- a/core/src/main/java/org/teavm/vm/TeaVMBuilder.java
+++ b/core/src/main/java/org/teavm/vm/TeaVMBuilder.java
@@ -17,11 +17,12 @@ package org.teavm.vm;
import org.teavm.interop.PlatformMarker;
import org.teavm.model.ClassHolderSource;
+import org.teavm.model.ClassReaderSource;
import org.teavm.parsing.ClasspathClassHolderSource;
public class TeaVMBuilder {
TeaVMTarget target;
- ClassHolderSource classSource;
+ ClassReaderSource classSource;
ClassLoader classLoader;
public TeaVMBuilder(TeaVMTarget target) {
@@ -30,7 +31,7 @@ public class TeaVMBuilder {
classSource = !isBootstrap() ? new ClasspathClassHolderSource(classLoader) : name -> null;
}
- public ClassHolderSource getClassSource() {
+ public ClassReaderSource getClassSource() {
return classSource;
}
diff --git a/html4j/pom.xml b/html4j/pom.xml
index 7a8db6e89..0e5d9778b 100644
--- a/html4j/pom.xml
+++ b/html4j/pom.xml
@@ -40,12 +40,6 @@
org.netbeans.html
net.java.html.boot
-
-
- org.ow2.asm
- asm
-
-
org.netbeans.html
diff --git a/html4j/src/main/java/org/teavm/html4j/testing/KOTestAdapter.java b/html4j/src/main/java/org/teavm/html4j/testing/KOTestAdapter.java
deleted file mode 100644
index f04418f90..000000000
--- a/html4j/src/main/java/org/teavm/html4j/testing/KOTestAdapter.java
+++ /dev/null
@@ -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 getExpectedExceptions(MethodReader method) {
- return Collections.emptyList();
- }
-
- @Override
- public Class extends TestRunner> getRunner(MethodReader method) {
- return KOTestRunner.class;
- }
-}
diff --git a/html4j/src/main/java/org/teavm/html4j/testing/KOTestRunner.java b/html4j/src/main/java/org/teavm/html4j/testing/KOTestRunner.java
deleted file mode 100644
index 76308ab1b..000000000
--- a/html4j/src/main/java/org/teavm/html4j/testing/KOTestRunner.java
+++ /dev/null
@@ -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);
- }
- }
- }
-}
diff --git a/tests/pom.xml b/tests/pom.xml
index 10ee9d67e..103872295 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -83,6 +83,21 @@
0.7.3
test
+
+ org.ow2.asm
+ asm-util
+ test
+
+
+ org.ow2.asm
+ asm-commons
+ test
+
+
+ org.mozilla
+ rhino
+ test
+
diff --git a/core/src/main/java/org/teavm/testing/SimpleTestRunner.java b/tests/src/test/java/org/teavm/tests/JUnit3BaseTest.java
similarity index 63%
rename from core/src/main/java/org/teavm/testing/SimpleTestRunner.java
rename to tests/src/test/java/org/teavm/tests/JUnit3BaseTest.java
index b63872844..fafb7ac8b 100644
--- a/core/src/main/java/org/teavm/testing/SimpleTestRunner.java
+++ b/tests/src/test/java/org/teavm/tests/JUnit3BaseTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Alexey Andreev.
+ * Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (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
* 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
- public void run(TestLauncher launcher) throws Throwable {
- launcher.launch();
+ protected void setUp() throws Exception {
+ super.setUp();
+ a = "start";
+ }
+
+ public void testFoo() {
+ assertEquals("start", a);
}
}
diff --git a/core/src/main/java/org/teavm/testing/TestAdapter.java b/tests/src/test/java/org/teavm/tests/JUnit3DerivedTest.java
similarity index 59%
rename from core/src/main/java/org/teavm/testing/TestAdapter.java
rename to tests/src/test/java/org/teavm/tests/JUnit3DerivedTest.java
index 7947d606e..423647e64 100644
--- a/core/src/main/java/org/teavm/testing/TestAdapter.java
+++ b/tests/src/test/java/org/teavm/tests/JUnit3DerivedTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2014 Alexey Andreev.
+ * Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (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
* 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 {
- boolean acceptClass(Class> cls);
+@RunWith(TeaVMTestRunner.class)
+public class JUnit3DerivedTest extends JUnit3BaseTest {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ b = "derived";
+ }
- boolean acceptMethod(MethodReader method);
-
- Iterable getExpectedExceptions(MethodReader method);
-
- Class extends TestRunner> getRunner(MethodReader method);
+ public void testBar() {
+ assertEquals("derived", b);
+ }
}
diff --git a/html4j/src/test/java/org/teavm/html4j/testing/KOTestAdapterTest.java b/tests/src/test/java/org/teavm/tests/JUnitBaseTest.java
similarity index 65%
rename from html4j/src/test/java/org/teavm/html4j/testing/KOTestAdapterTest.java
rename to tests/src/test/java/org/teavm/tests/JUnitBaseTest.java
index 43d104e4e..d81eb95a3 100644
--- a/html4j/src/test/java/org/teavm/html4j/testing/KOTestAdapterTest.java
+++ b/tests/src/test/java/org/teavm/tests/JUnitBaseTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 Jaroslav Tulach.
+ * Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (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
* 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;
-/**
- *
- * @author Jaroslav Tulach
- */
-public class KOTestAdapterTest {
+public abstract class JUnitBaseTest {
+ String a;
+ String b;
+
+ @Before
+ public void startBase() {
+ a = "start";
+ }
+
@Test
- public void verifyKOTestClassNameIsCorrect() throws ClassNotFoundException {
- Class.forName(KOTestAdapter.KO_TEST_CLASS);
+ public void foo() {
+ assertEquals("start", a);
}
}
diff --git a/core/src/main/java/org/teavm/testing/TestLauncher.java b/tests/src/test/java/org/teavm/tests/JUnitDerivedTest.java
similarity index 55%
rename from core/src/main/java/org/teavm/testing/TestLauncher.java
rename to tests/src/test/java/org/teavm/tests/JUnitDerivedTest.java
index decc7ee35..f704067a1 100644
--- a/core/src/main/java/org/teavm/testing/TestLauncher.java
+++ b/tests/src/test/java/org/teavm/tests/JUnitDerivedTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2015 Alexey Andreev.
+ * Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (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
* limitations under the License.
*/
-package org.teavm.testing;
+package org.teavm.tests;
-public interface TestLauncher {
- void launch() throws Throwable;
+import static org.junit.Assert.assertEquals;
+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);
+ }
}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java
index 24458bc71..612ca9f24 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java
@@ -15,17 +15,21 @@
*/
package org.teavm.junit;
+import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -41,7 +45,12 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Stream;
+import junit.framework.TestCase;
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.Runner;
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.wasm.WasmTarget;
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.Problem;
+import org.teavm.model.AnnotationHolder;
+import org.teavm.model.AnnotationValue;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
+import org.teavm.model.MethodReference;
import org.teavm.model.PreOptimizingClassHolderSource;
import org.teavm.model.ValueType;
import org.teavm.parsing.ClasspathClassHolderSource;
-import org.teavm.testing.JUnitTestAdapter;
-import org.teavm.testing.TestAdapter;
import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.vm.DirectoryBuildTarget;
import org.teavm.vm.TeaVM;
@@ -72,6 +84,12 @@ import org.teavm.vm.TeaVMBuilder;
import org.teavm.vm.TeaVMTarget;
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 JS_RUNNER = "teavm.junit.js.runner";
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 Class> testClass;
- private ClassHolder classHolder;
+ private ClassHolderSource classSource;
private ClassLoader classLoader;
private Description suiteDescription;
private static Map classSources = new WeakHashMap<>();
private File outputDir;
- private TestAdapter testAdapter = new JUnitTestAdapter();
private Map descriptions = new HashMap<>();
private static Map runners = new HashMap<>();
private static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
@@ -124,8 +141,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
public TeaVMTestRunner(Class> testClass) throws InitializationError {
this.testClass = testClass;
classLoader = TeaVMTestRunner.class.getClassLoader();
- ClassHolderSource classSource = getClassSource(classLoader);
- classHolder = classSource.get(testClass.getName());
+ classSource = getClassSource(classLoader);
String outputPath = System.getProperty(PATH_PARAM);
if (outputPath != null) {
outputDir = new File(outputPath);
@@ -166,7 +182,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
if (suiteDescription == null) {
suiteDescription = Description.createSuiteDescription(testClass);
for (Method child : getFilteredChildren()) {
- suiteDescription.getChildren().add(describeChild(child));
+ suiteDescription.addChild(describeChild(child));
}
}
return suiteDescription;
@@ -196,15 +212,29 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private List getChildren() {
List children = new ArrayList<>();
- for (Method method : testClass.getDeclaredMethods()) {
- MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
- if (testAdapter.acceptMethod(methodHolder)) {
- children.add(method);
+ Class> cls = testClass;
+ Set foundMethods = new HashSet<>();
+ while (cls != Object.class && !cls.getName().equals(JUNIT3_BASE_CLASS)) {
+ for (Method method : cls.getDeclaredMethods()) {
+ if (foundMethods.add(method.getName()) && isTestMethod(method)) {
+ children.add(method);
+ }
}
+ cls = cls.getSuperclass();
}
+
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 getFilteredChildren() {
if (filteredChildren == null) {
filteredChildren = getChildren();
@@ -218,31 +248,39 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}
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 success = true;
+ ClassHolder classHolder = classSource.get(child.getDeclaringClass().getName());
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(child));
Set> expectedExceptions = new HashSet<>();
- for (String exceptionName : testAdapter.getExpectedExceptions(methodHolder)) {
+ for (String exceptionName : getExpectedExceptions(methodHolder)) {
try {
expectedExceptions.add(Class.forName(exceptionName, false, classLoader));
} catch (ClassNotFoundException e) {
- notifier.fireTestFailure(new Failure(describeChild(child), e));
- notifier.fireTestFinished(describeChild(child));
+ notifier.fireTestFailure(new Failure(description, e));
+ notifier.fireTestFinished(description);
latch.countDown();
return;
}
}
if (!child.isAnnotationPresent(SkipJVM.class)
- && !child.getDeclaringClass().isAnnotationPresent(SkipJVM.class)) {
+ && !testClass.isAnnotationPresent(SkipJVM.class)) {
ran = true;
success = runInJvm(child, notifier, expectedExceptions);
}
- Description description = describeChild(child);
+
if (success && outputDir != null) {
int[] configurationIndex = new int[] { 0 };
List> 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> expectedExceptions) {
+ Description description = describeChild(child);
+ Runner runner;
Object instance;
try {
instance = testClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
- notifier.fireTestFailure(new Failure(describeChild(child), e));
+ notifier.fireTestFailure(new Failure(description, e));
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;
- try {
- child.invoke(instance);
- } catch (IllegalAccessException e) {
- notifier.fireTestFailure(new Failure(describeChild(child), e));
- return false;
- } catch (InvocationTargetException e) {
- boolean wasExpected = false;
- for (Class> expected : expectedExceptions) {
- if (expected.isInstance(e.getTargetException())) {
- expectedCaught = true;
- wasExpected = true;
+ List> classes = new ArrayList<>();
+ Class> cls = instance.getClass();
+ while (cls != null) {
+ classes.add(cls);
+ cls = cls.getSuperclass();
+ }
+ Collections.reverse(classes);
+ for (Class> c : classes) {
+ for (Method method : c.getMethods()) {
+ if (method.isAnnotationPresent(Before.class)) {
+ try {
+ 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;
}
+
+ 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()) {
- notifier.fireTestAssumptionFailed(new Failure(describeChild(child),
- new AssertionError("Expected exception was not thrown")));
- return false;
+ @Override
+ public void run() throws Throwable {
+ try {
+ 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,
@@ -416,7 +548,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private File getOutputPath(Method method) {
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.mkdirs();
return path;
@@ -430,27 +562,46 @@ public class TeaVMTestRunner extends Runner implements Filterable {
private CompileResult compileToJs(Method method, TeaVMTestConfiguration configuration,
File path) {
- return compileTest(method, configuration, JavaScriptTarget::new, vm -> {
- vm.entryPoint(TestEntryPoint.class.getName());
- }, path, ".js");
+ DebugInformationBuilder debugEmitter = new DebugInformationBuilder();
+ Supplier targetSupplier = () -> {
+ 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 configuration,
File path) {
- return compileTest(method, configuration, CTarget::new, vm -> {
- vm.entryPoint(TestNativeEntryPoint.class.getName());
- }, path, ".c");
+ return compileTest(method, configuration, CTarget::new, TestNativeEntryPoint.class.getName(), path, ".c", null);
}
private CompileResult compileToWasm(Method method, TeaVMTestConfiguration configuration,
File path) {
- return compileTest(method, configuration, WasmTarget::new, vm -> {
- vm.entryPoint(TestNativeEntryPoint.class.getName());
- }, path, ".wasm");
+ return compileTest(method, configuration, WasmTarget::new, TestNativeEntryPoint.class.getName(), path,
+ ".wasm", null);
}
private CompileResult compileTest(Method method, TeaVMTestConfiguration configuration,
- Supplier targetSupplier, Consumer preBuild, File path, String extension) {
+ Supplier targetSupplier, String entryPoint, File path, String extension,
+ CompilePostProcessor postBuild) {
CompileResult result = new CompileResult();
StringBuilder simpleName = new StringBuilder();
@@ -464,10 +615,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
result.file = outputFile;
ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader();
- ClassHolderSource classSource = getClassSource(classLoader);
+ ClassHolder classHolder = classSource.get(method.getDeclaringClass().getName());
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
- Class> runnerType = testAdapter.getRunner(methodHolder);
T target = targetSupplier.get();
configuration.apply(target);
@@ -486,18 +636,26 @@ public class TeaVMTestRunner extends Runner implements Filterable {
vm.installPlugins();
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());
if (!vm.getProblemProvider().getProblems().isEmpty()) {
result.success = false;
result.errorMessage = buildErrorMessage(vm);
+ } else {
+ if (postBuild != null) {
+ postBuild.process(vm, outputFile);
+ }
}
return result;
}
+ interface CompilePostProcessor {
+ void process(TeaVM vm, File targetFile);
+ }
+
private List> getJavaScriptConfigurations() {
List> configurations = new ArrayList<>();
if (Boolean.parseBoolean(System.getProperty(JS_ENABLED, "true"))) {
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java
index 4960c6e15..89590228c 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java
@@ -15,8 +15,6 @@
*/
package org.teavm.junit;
-import org.teavm.testing.TestRunner;
-
final class TestEntryPoint {
private static Object testCase;
@@ -24,14 +22,23 @@ final class TestEntryPoint {
}
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 boolean isExpectedException(Class> cls);
+ private static native void after();
public static void main(String[] args) throws Throwable {
run();
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java
index b089b40ac..30850f521 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java
@@ -15,13 +15,22 @@
*/
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.model.AnnotationReader;
import org.teavm.model.AnnotationValue;
import org.teavm.model.BasicBlock;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
+import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.MethodHolder;
@@ -36,12 +45,12 @@ import org.teavm.vm.spi.TeaVMHost;
import org.teavm.vm.spi.TeaVMPlugin;
class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
- private String runnerClassName;
private MethodReference testMethod;
+ private String testClassName;
- TestEntryPointTransformer(String runnerClassName, MethodReference testMethod) {
- this.runnerClassName = runnerClassName;
+ TestEntryPointTransformer(MethodReference testMethod, String testClassName) {
this.testMethod = testMethod;
+ this.testClassName = testClassName;
}
@Override
@@ -53,38 +62,89 @@ class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
public void transformClass(ClassHolder cls, ClassReaderSource innerSource, Diagnostics diagnostics) {
if (cls.getName().equals(TestEntryPoint.class.getName())) {
for (MethodHolder method : cls.getMethods()) {
- 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);
+ switch (method.getName()) {
+ case "launchTest":
+ method.setProgram(generateLaunchProgram(method, innerSource));
+ method.getModifiers().remove(ElementModifier.NATIVE);
+ break;
+ 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);
- 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 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();
}
+ private Program generateAfterProgram(MethodHolder method, ClassReaderSource innerSource) {
+ ProgramEmitter pe = ProgramEmitter.create(method, innerSource);
+ ValueEmitter testCaseVar = pe.getField(TestEntryPoint.class, "testCase", Object.class);
+
+ List 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 collectSuperClasses(ClassReaderSource classSource, String className) {
+ List 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) {
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)
.cast(ValueType.object(testMethod.getClassName()))
.invokeSpecial(testMethod);
MethodReader testMethodReader = innerSource.resolve(testMethod);
- AnnotationReader testAnnotation = testMethodReader.getAnnotations().get(Test.class.getName());
- AnnotationValue throwsValue = testAnnotation.getValue("expected");
+ AnnotationReader testAnnotation = testMethodReader.getAnnotations().get(JUNIT4_TEST);
+ AnnotationValue throwsValue = testAnnotation != null ? testAnnotation.getValue("expected") : null;
if (throwsValue != null) {
BasicBlock handler = pe.getProgram().createBasicBlock();
TryCatchBlock tryCatch = new TryCatchBlock();