diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml
index f065a51e0..533446d3f 100644
--- a/.idea/codeStyleSettings.xml
+++ b/.idea/codeStyleSettings.xml
@@ -20,6 +20,13 @@
+
+
+
+
+
+
+
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 48910a483..9d56aee03 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,7 @@
+
diff --git a/jso/apis/pom.xml b/jso/apis/pom.xml
index 62b139628..ad807c4ed 100644
--- a/jso/apis/pom.xml
+++ b/jso/apis/pom.xml
@@ -71,13 +71,6 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs
org.apache.maven.plugins
maven-javadoc-plugin
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
- true
-
-
diff --git a/tests/pom.xml b/tests/pom.xml
index 0ace7653a..797d3cc49 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -83,43 +83,6 @@
-
- org.teavm
- teavm-maven-plugin
- ${project.version}
-
-
- generate-javascript-tests
-
- testCompile
-
-
- false
- true
-
- org.teavm.classlib.**.*Test
- org.teavm.jso.**.*Test
- org.teavm.platform.metadata.*Test
-
-
- en, en_US, en_GB, ru, ru_RU
-
- ${teavm.test.incremental}
-
-
-
- run-javascript-tests
-
- test
-
-
- ${teavm.test.skip}
- ${teavm.test.threads}
- ${teavm.test.selenium}
-
-
-
-
org.apache.maven.plugins
maven-surefire-plugin
diff --git a/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatParseTest.java b/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatParseTest.java
index 1e81674ed..73373ef27 100644
--- a/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatParseTest.java
+++ b/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatParseTest.java
@@ -23,9 +23,12 @@ import java.text.ParseException;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.teavm.junit.TeaVMProperties;
+import org.teavm.junit.TeaVMProperty;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@TeaVMProperties(@TeaVMProperty(key = "java.util.Locale.available", value = "en, en_US, en_GB, ru, ru_RU"))
public class DecimalFormatParseTest {
private static DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
diff --git a/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java b/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java
index 008c5e06f..9a4a89e31 100644
--- a/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java
+++ b/tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java
@@ -25,9 +25,12 @@ import java.util.Currency;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.teavm.junit.TeaVMProperties;
+import org.teavm.junit.TeaVMProperty;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@TeaVMProperties(@TeaVMProperty(key = "java.util.Locale.available", value = "en, en_US, en_GB, ru, ru_RU"))
public class DecimalFormatTest {
private static DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
diff --git a/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java b/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java
index 20af526d8..48997d661 100644
--- a/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java
+++ b/tests/src/test/java/org/teavm/classlib/java/text/NumberFormatTest.java
@@ -7,6 +7,7 @@ import java.util.Locale;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
@@ -32,7 +33,7 @@ public class NumberFormatTest {
}
@Test
- @Ignore
+ @SkipJVM
public void formatsPercent() {
NumberFormat format = NumberFormat.getPercentInstance(new Locale("en", "US"));
assertEquals("12,345,679%", format.format(123456.789123));
diff --git a/tests/src/test/java/org/teavm/classlib/java/util/CurrencyTest.java b/tests/src/test/java/org/teavm/classlib/java/util/CurrencyTest.java
index f06445609..17223a7f2 100644
--- a/tests/src/test/java/org/teavm/classlib/java/util/CurrencyTest.java
+++ b/tests/src/test/java/org/teavm/classlib/java/util/CurrencyTest.java
@@ -21,9 +21,13 @@ import java.util.Locale;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.teavm.junit.SkipJVM;
+import org.teavm.junit.TeaVMProperties;
+import org.teavm.junit.TeaVMProperty;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@TeaVMProperties(@TeaVMProperty(key = "java.util.Locale.available", value = "en, en_US, en_GB, ru, ru_RU"))
public class CurrencyTest {
@Test
public void findsByCode() {
@@ -46,8 +50,7 @@ public class CurrencyTest {
}
@Test
- @Ignore
- // It seems that JDK can't translate currency names into Russian
+ @SkipJVM
public void getsDisplayName() {
Locale russian = new Locale("ru");
Locale english = new Locale("en");
@@ -64,8 +67,7 @@ public class CurrencyTest {
}
@Test
- @Ignore
- // It seems that JDK does not know about currency symbols
+ @SkipJVM
public void getsSymbol() {
Locale russian = new Locale("ru");
Locale english = new Locale("en");
diff --git a/tests/src/test/java/org/teavm/classlib/java/util/LocaleTest.java b/tests/src/test/java/org/teavm/classlib/java/util/LocaleTest.java
index b7c2b2b74..2dce12d9f 100644
--- a/tests/src/test/java/org/teavm/classlib/java/util/LocaleTest.java
+++ b/tests/src/test/java/org/teavm/classlib/java/util/LocaleTest.java
@@ -19,9 +19,12 @@ import static org.junit.Assert.*;
import java.util.Locale;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.teavm.junit.TeaVMProperties;
+import org.teavm.junit.TeaVMProperty;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@TeaVMProperties(@TeaVMProperty(key = "java.util.Locale.available", value = "en, en_US, en_GB, ru, ru_RU"))
public class LocaleTest {
@Test
public void availableLocalesFound() {
diff --git a/tests/src/test/java/org/teavm/classlib/java/util/TestServiceImpl.java b/tests/src/test/java/org/teavm/classlib/java/util/TestServiceImpl.java
index 33f117ae2..6c8b571ec 100644
--- a/tests/src/test/java/org/teavm/classlib/java/util/TestServiceImpl.java
+++ b/tests/src/test/java/org/teavm/classlib/java/util/TestServiceImpl.java
@@ -15,14 +15,6 @@
*/
package org.teavm.classlib.java.util;
-import org.junit.runner.RunWith;
-import org.teavm.junit.TeaVMTestRunner;
-
-/**
- *
- * @author Alexey Andreev
- */
-@RunWith(TeaVMTestRunner.class)
public class TestServiceImpl implements TestService {
private int counter;
diff --git a/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java b/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java
index 9916d3089..3e521afac 100644
--- a/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java
+++ b/tests/src/test/java/org/teavm/jso/test/AnnotationsTest.java
@@ -22,9 +22,11 @@ import org.teavm.jso.JSBody;
import org.teavm.jso.JSMethod;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@SkipJVM
public class AnnotationsTest {
@Test
public void staticBodyWorks() {
diff --git a/tests/src/test/java/org/teavm/jso/test/ConversionTest.java b/tests/src/test/java/org/teavm/jso/test/ConversionTest.java
index 8a806a291..62b0cb17d 100644
--- a/tests/src/test/java/org/teavm/jso/test/ConversionTest.java
+++ b/tests/src/test/java/org/teavm/jso/test/ConversionTest.java
@@ -22,9 +22,11 @@ import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSProperty;
import org.teavm.jso.core.JSString;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@SkipJVM
public class ConversionTest {
@Test
public void convertsPrimitivesToJavaScript() {
diff --git a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java
index 423260bb5..97163a828 100644
--- a/tests/src/test/java/org/teavm/jso/test/FunctorTest.java
+++ b/tests/src/test/java/org/teavm/jso/test/FunctorTest.java
@@ -21,9 +21,11 @@ import org.junit.runner.RunWith;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@SkipJVM
public class FunctorTest {
@Test
public void functorPassed() {
diff --git a/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java b/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java
index 26c10e79c..11a9dbfe7 100644
--- a/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java
+++ b/tests/src/test/java/org/teavm/jso/test/ImplementationTest.java
@@ -20,13 +20,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
-/**
- *
- * @author Alexey Andreev
- */
@RunWith(TeaVMTestRunner.class)
+@SkipJVM
public class ImplementationTest {
@Test
public void respectsPrecedence() {
@@ -35,10 +33,10 @@ public class ImplementationTest {
}
@JSBody(params = { "a", "b" }, script = "return a + b;")
- static final native int add(int a, int b);
+ static native int add(int a, int b);
@JSBody(params = { "a", "b" }, script = "return a * b;")
- static final native int mul(int a, int b);
+ static native int mul(int a, int b);
@Test
public void inliningUsageCounterWorksProperly() {
diff --git a/tests/src/test/java/org/teavm/jso/test/JavaInvocationTest.java b/tests/src/test/java/org/teavm/jso/test/JavaInvocationTest.java
index 1aa080992..9105b80fe 100644
--- a/tests/src/test/java/org/teavm/jso/test/JavaInvocationTest.java
+++ b/tests/src/test/java/org/teavm/jso/test/JavaInvocationTest.java
@@ -20,9 +20,11 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSObject;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
@RunWith(TeaVMTestRunner.class)
+@SkipJVM
public class JavaInvocationTest {
@Test
public void callStaticMethod() {
diff --git a/tests/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java b/tests/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java
index 49e4fee5c..5978940f3 100644
--- a/tests/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java
+++ b/tests/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java
@@ -18,6 +18,7 @@ package org.teavm.platform.metadata;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
/**
@@ -25,6 +26,7 @@ import org.teavm.junit.TeaVMTestRunner;
* @author Alexey Andreev
*/
@RunWith(TeaVMTestRunner.class)
+@SkipJVM
public class MetadataGeneratorTest {
@MetadataProvider(TestResourceGenerator.class)
private native TestResource getNull();
diff --git a/tests/src/test/java/org/teavm/tests/JSOTest.java b/tests/src/test/java/org/teavm/tests/JSOTest.java
index b68154eab..7bfb6b79c 100644
--- a/tests/src/test/java/org/teavm/tests/JSOTest.java
+++ b/tests/src/test/java/org/teavm/tests/JSOTest.java
@@ -22,17 +22,13 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.teavm.diagnostics.Problem;
import org.teavm.jso.JSBody;
+import org.teavm.junit.SkipJVM;
import org.teavm.junit.TeaVMTestRunner;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.vm.TeaVM;
import org.teavm.vm.TeaVMBuilder;
-/**
- *
- * @author Alexey Andreev
- */
-@RunWith(TeaVMTestRunner.class)
public class JSOTest {
@Test
public void reportsAboutWrongParameterOfJSBody() {
diff --git a/tools/core/src/main/java/org/teavm/tooling/TeaVMProblemRenderer.java b/tools/core/src/main/java/org/teavm/tooling/TeaVMProblemRenderer.java
index c2ce79c62..18a056d96 100644
--- a/tools/core/src/main/java/org/teavm/tooling/TeaVMProblemRenderer.java
+++ b/tools/core/src/main/java/org/teavm/tooling/TeaVMProblemRenderer.java
@@ -55,7 +55,7 @@ public final class TeaVMProblemRenderer {
}
}
- private static void renderCallStack(CallGraph cg, CallLocation location, StringBuilder sb) {
+ public static void renderCallStack(CallGraph cg, CallLocation location, StringBuilder sb) {
if (location == null) {
return;
}
@@ -76,7 +76,7 @@ public final class TeaVMProblemRenderer {
}
}
- private static void renderCallLocation(MethodReference method, InstructionLocation location, StringBuilder sb) {
+ public static void renderCallLocation(MethodReference method, InstructionLocation location, StringBuilder sb) {
if (method != null) {
sb.append(method.getClassName() + "." + method.getName());
} else {
diff --git a/tools/junit/src/main/java/org/teavm/junit/ExceptionHelper.java b/tools/junit/src/main/java/org/teavm/junit/ExceptionHelper.java
new file mode 100644
index 000000000..d9d88435a
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/ExceptionHelper.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016 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.junit;
+
+/**
+ *
+ * @author Alexey Andreev
+ */
+final class ExceptionHelper {
+ private ExceptionHelper() {
+ }
+
+ public static String showException(Throwable e) {
+ return e.getMessage();
+ }
+}
diff --git a/tools/junit/src/main/java/org/teavm/junit/SkipJVM.java b/tools/junit/src/main/java/org/teavm/junit/SkipJVM.java
new file mode 100644
index 000000000..8787d1721
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/SkipJVM.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 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.junit;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface SkipJVM {
+}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMProperties.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMProperties.java
new file mode 100644
index 000000000..ee4270837
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMProperties.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016 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.junit;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface TeaVMProperties {
+ TeaVMProperty[] value();
+}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TeaVMProperty.java b/tools/junit/src/main/java/org/teavm/junit/TeaVMProperty.java
new file mode 100644
index 000000000..d94045efd
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMProperty.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2016 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.junit;
+
+public @interface TeaVMProperty {
+ String key();
+
+ String value();
+}
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 708a67c14..66983c754 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java
@@ -16,72 +16,258 @@
package org.teavm.junit;
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 org.junit.Test;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.stream.Stream;
+import org.apache.commons.io.IOUtils;
import org.junit.runner.Description;
-import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+import org.teavm.callgraph.CallGraph;
+import org.teavm.diagnostics.DefaultProblemTextConsumer;
+import org.teavm.diagnostics.Problem;
+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.tooling.testing.TeaVMTestTool;
+import org.teavm.vm.DirectoryBuildTarget;
+import org.teavm.vm.TeaVM;
+import org.teavm.vm.TeaVMBuilder;
-public class TeaVMTestRunner extends Runner {
+public class TeaVMTestRunner extends ParentRunner {
private static final String PATH_PARAM = "teavm.junit.target";
- private Class> testClass;
- private Description description;
+ private ClassHolder classHolder;
+ private ClassLoader classLoader;
+ private ClassHolderSource classSource;
+ private static Map classSources = new WeakHashMap<>();
+ private File outputDir;
+ private TestAdapter testAdapter = new JUnitTestAdapter();
+ private Map descriptions = new HashMap<>();
- public TeaVMTestRunner(Class> testClass) {
- this.testClass = testClass;
+ public TeaVMTestRunner(Class> testClass) throws InitializationError {
+ super(testClass);
+ classLoader = TeaVMTestRunner.class.getClassLoader();
+ classSource = getClassSource(classLoader);
+ classHolder = classSource.get(testClass.getName());
+ String outputPath = System.getProperty(PATH_PARAM);
+ if (outputPath != null) {
+ outputDir = new File(outputPath);
+ }
}
@Override
- public Description getDescription() {
- if (description == null) {
- description = Description.createSuiteDescription(testClass);
- for (Method method : testClass.getMethods()) {
- if (method.getParameterCount() == 0 && method.getReturnType() == void.class
- && method.isAnnotationPresent(Test.class)) {
- Description testDescription = Description.createTestDescription(testClass, method.getName());
- description.addChild(testDescription);
+ protected List getChildren() {
+ List children = new ArrayList<>();
+ for (Method method : getTestClass().getJavaClass().getDeclaredMethods()) {
+ MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
+ if (testAdapter.acceptMethod(methodHolder)) {
+ children.add(method);
+ }
+ }
+ return children;
+ }
+
+ @Override
+ protected Description describeChild(Method child) {
+ return descriptions.computeIfAbsent(child, method -> Description.createTestDescription(
+ getTestClass().getJavaClass(), method.getName()));
+ }
+
+ @Override
+ protected void runChild(Method child, RunNotifier notifier) {
+ notifier.fireTestStarted(describeChild(child));
+
+ boolean run = false;
+ boolean success = true;
+ if (outputDir != null) {
+ run = true;
+ success &= runInTeaVM(child, notifier);
+ }
+
+ if (success && !child.isAnnotationPresent(SkipJVM.class)
+ && !child.getDeclaringClass().isAnnotationPresent(SkipJVM.class)) {
+ run = true;
+ success &= runInJvm(child, notifier);
+ }
+
+ if (!run) {
+ notifier.fireTestIgnored(describeChild(child));
+ }
+ notifier.fireTestFinished(describeChild(child));
+ }
+
+ private boolean runInJvm(Method child, RunNotifier notifier) {
+ MethodHolder methodHolder = classHolder.getMethod(getDescriptor(child));
+ Set> expectedExceptions = new HashSet<>();
+ for (String exceptionName : testAdapter.getExpectedExceptions(methodHolder)) {
+ try {
+ expectedExceptions.add(Class.forName(exceptionName, false, classLoader));
+ } catch (ClassNotFoundException e) {
+ notifier.fireTestFailure(new Failure(describeChild(child), e));
+ return false;
+ }
+ }
+
+ Object instance;
+ try {
+ instance = getTestClass().getJavaClass().newInstance();
+ } catch (InstantiationException | IllegalAccessException e) {
+ notifier.fireTestFailure(new Failure(describeChild(child), e));
+ return false;
+ }
+
+ 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;
}
}
+ if (!wasExpected) {
+ notifier.fireTestFailure(new Failure(describeChild(child), e.getTargetException()));
+ return false;
+ }
}
- return description;
+
+ if (!expectedCaught && !expectedExceptions.isEmpty()) {
+ notifier.fireTestAssumptionFailed(new Failure(describeChild(child),
+ new AssertionError("Expected exception was not thrown")));
+ return false;
+ }
+
+ return true;
}
- @Override
- public void run(RunNotifier notifier) {
- Description description = getDescription();
-
- notifier.fireTestStarted(description);
- String targetPath = System.getProperty(PATH_PARAM);
- if (targetPath == null) {
- for (Description testDescription : description.getChildren()) {
- notifier.fireTestIgnored(testDescription);
- }
- notifier.fireTestIgnored(description);
- notifier.fireTestFinished(description);
- return;
- }
-
- TeaVMTestTool tool = new TeaVMTestTool();
- tool.setTargetDirectory(new File(targetPath, testClass.getName()));
- tool.setMinifying(false);
- tool.getTestClasses().add(testClass.getName());
- boolean success = true;
+ private boolean runInTeaVM(Method child, RunNotifier notifier) {
+ CompileResult compileResult;
try {
- tool.generate();
- } catch (Exception e) {
- notifier.fireTestFailure(new Failure(description, e));
- success = false;
+ compileResult = compileTest(child);
+ } catch (IOException e) {
+ notifier.fireTestFailure(new Failure(describeChild(child), e));
+ return false;
}
- if (success) {
- for (Description testDescription : description.getChildren()) {
- notifier.fireTestStarted(testDescription);
- notifier.fireTestFinished(testDescription);
+ if (!compileResult.success) {
+ notifier.fireTestFailure(new Failure(describeChild(child),
+ new AssertionError(compileResult.errorMessage)));
+ return false;
+ }
+
+ return true;
+ }
+
+ private CompileResult compileTest(Method method) throws IOException {
+ CompileResult result = new CompileResult();
+
+ File path = outputDir;
+ path = new File(path, method.getDeclaringClass().getName().replace('.', '/'));
+ path = new File(path, method.getName());
+ path.mkdirs();
+ File outputFile = new File(path, "test.js");
+ result.file = outputFile;
+
+ resourceToFile("org/teavm/javascript/runtime.js", new File(path, "runtime.js"));
+ resourceToFile("teavm-run-test.html", new File(path, "run-test.html"));
+
+ ClassLoader classLoader = TeaVMTestRunner.class.getClassLoader();
+ ClassHolderSource classSource = getClassSource(classLoader);
+
+ MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
+ Class> runnerType = testAdapter.getRunner(methodHolder);
+
+ TeaVM vm = new TeaVMBuilder()
+ .setClassLoader(classLoader)
+ .setClassSource(classSource)
+ .build();
+ vm.setIncremental(false);
+ vm.setMinifying(false);
+ vm.installPlugins();
+
+ new TestExceptionPlugin().install(vm);
+ new TestEntryPointTransformer(runnerType.getName(), methodHolder.getReference()).install(vm);
+
+ try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8")) {
+ MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException",
+ Throwable.class, String.class);
+ vm.entryPoint("runTest", new MethodReference(TestEntryPoint.class, "run", void.class)).async();
+ vm.entryPoint("extractException", exceptionMsg);
+ vm.build(innerWriter, new DirectoryBuildTarget(outputDir));
+ if (!vm.getProblemProvider().getProblems().isEmpty()) {
+ result.success = false;
+ result.errorMessage = buildErrorMessage(vm);
}
}
- notifier.fireTestFinished(description);
+ return result;
+ }
+
+ private MethodDescriptor getDescriptor(Method method) {
+ ValueType[] signature = Stream.concat(Arrays.stream(method.getParameterTypes()).map(ValueType::parse),
+ Stream.of(ValueType.parse(method.getReturnType())))
+ .toArray(ValueType[]::new);
+ return new MethodDescriptor(method.getName(), signature);
+ }
+
+ private String buildErrorMessage(TeaVM vm) {
+ CallGraph cg = vm.getDependencyInfo().getCallGraph();
+ DefaultProblemTextConsumer consumer = new DefaultProblemTextConsumer();
+ StringBuilder sb = new StringBuilder();
+ for (Problem problem : vm.getProblemProvider().getProblems()) {
+ consumer.clear();
+ problem.render(consumer);
+ sb.append(consumer.getText());
+ TeaVMProblemRenderer.renderCallStack(cg, problem.getLocation(), sb);
+ sb.append("\n");
+ }
+ return sb.toString();
+ }
+
+ private void resourceToFile(String resource, File fileName) throws IOException {
+ try (InputStream input = TeaVMTestTool.class.getClassLoader().getResourceAsStream(resource);
+ OutputStream output = new FileOutputStream(fileName)) {
+ IOUtils.copy(input, output);
+ }
+ }
+
+ private static ClassHolderSource getClassSource(ClassLoader classLoader) {
+ return classSources.computeIfAbsent(classLoader, cl -> new PreOptimizingClassHolderSource(
+ new ClasspathClassHolderSource(classLoader)));
+ }
+
+ static class CompileResult {
+ boolean success = true;
+ String errorMessage;
+ File file;
}
}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java
new file mode 100644
index 000000000..1f5fd4a44
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016 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.junit;
+
+import org.teavm.testing.TestRunner;
+
+final class TestEntryPoint {
+ private static Object testCase;
+
+ private TestEntryPoint() {
+ }
+
+ public static void run() throws Throwable {
+ createRunner().run(() -> launchTest());
+ }
+
+ private static native TestRunner createRunner();
+
+ private static native void launchTest();
+
+ private static native boolean isExpectedException(Class> cls);
+}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java
new file mode 100644
index 000000000..ae1c27cc9
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 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.junit;
+
+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.ValueType;
+import org.teavm.model.emit.ProgramEmitter;
+import org.teavm.model.emit.ValueEmitter;
+import org.teavm.vm.spi.TeaVMHost;
+import org.teavm.vm.spi.TeaVMPlugin;
+
+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.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);
+ 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);
+ pe.exit();
+ return pe.getProgram();
+ }
+}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependency.java b/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependency.java
new file mode 100644
index 000000000..6eab0bbfd
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/TestExceptionDependency.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2016 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.junit;
+
+import org.teavm.dependency.AbstractDependencyListener;
+import org.teavm.dependency.DependencyAgent;
+import org.teavm.dependency.DependencyNode;
+import org.teavm.dependency.MethodDependency;
+import org.teavm.model.CallLocation;
+import org.teavm.model.ClassReader;
+import org.teavm.model.ClassReaderSource;
+import org.teavm.model.MethodReference;
+
+class TestExceptionDependency extends AbstractDependencyListener {
+ private MethodReference getMessageRef = new MethodReference(ExceptionHelper.class, "showException",
+ Throwable.class, String.class);
+ private DependencyNode allClasses;
+
+ @Override
+ public void started(DependencyAgent agent) {
+ allClasses = agent.createNode();
+ }
+
+ @Override
+ public void classReached(DependencyAgent agent, String className, CallLocation location) {
+ if (isException(agent.getClassSource(), className)) {
+ allClasses.propagate(agent.getType(className));
+ }
+ }
+
+ private boolean isException(ClassReaderSource classSource, String className) {
+ while (className != null) {
+ if (className.equals("java.lang.Throwable")) {
+ return true;
+ }
+ ClassReader cls = classSource.get(className);
+ if (cls == null) {
+ return false;
+ }
+ className = cls.getParent();
+ }
+ return false;
+ }
+
+ @Override
+ public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
+ if (method.getReference().equals(getMessageRef)) {
+ allClasses.connect(method.getVariable(1));
+ }
+ }
+}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java b/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java
new file mode 100644
index 000000000..b274585fe
--- /dev/null
+++ b/tools/junit/src/main/java/org/teavm/junit/TestExceptionPlugin.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2016 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.junit;
+
+import org.teavm.vm.spi.TeaVMHost;
+import org.teavm.vm.spi.TeaVMPlugin;
+
+class TestExceptionPlugin implements TeaVMPlugin {
+ @Override
+ public void install(TeaVMHost host) {
+ host.add(new TestExceptionDependency());
+ }
+}
diff --git a/tools/maven/plugin/src/main/resources/teavm-htmlunit-adapter.js b/tools/junit/src/main/resources/teavm-htmlunit-adapter.js
similarity index 100%
rename from tools/maven/plugin/src/main/resources/teavm-htmlunit-adapter.js
rename to tools/junit/src/main/resources/teavm-htmlunit-adapter.js
diff --git a/tools/junit/src/main/resources/teavm-run-test.html b/tools/junit/src/main/resources/teavm-run-test.html
new file mode 100644
index 000000000..659e14466
--- /dev/null
+++ b/tools/junit/src/main/resources/teavm-run-test.html
@@ -0,0 +1,53 @@
+
+
+
+ TeaVM JUnit test
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/maven/plugin/src/main/resources/teavm-selenium-adapter.js b/tools/junit/src/main/resources/teavm-selenium-adapter.js
similarity index 100%
rename from tools/maven/plugin/src/main/resources/teavm-selenium-adapter.js
rename to tools/junit/src/main/resources/teavm-selenium-adapter.js
diff --git a/tools/maven/plugin/src/main/resources/teavm-selenium.js b/tools/junit/src/main/resources/teavm-selenium.js
similarity index 100%
rename from tools/maven/plugin/src/main/resources/teavm-selenium.js
rename to tools/junit/src/main/resources/teavm-selenium.js