diff --git a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java
index 0fd809a65..e09675e0a 100644
--- a/core/src/main/java/org/teavm/dependency/DependencyClassSource.java
+++ b/core/src/main/java/org/teavm/dependency/DependencyClassSource.java
@@ -140,5 +140,10 @@ class DependencyClassSource implements ClassHolderSource {
public boolean isStrict() {
return strict;
}
+
+ @Override
+ public void submit(ClassHolder cls) {
+ DependencyClassSource.this.submit(cls);
+ }
};
}
diff --git a/core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java b/core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java
index f3831a304..ce0e1a4f2 100644
--- a/core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java
+++ b/core/src/main/java/org/teavm/dependency/VirtualCallConsumer.java
@@ -58,13 +58,7 @@ class VirtualCallConsumer implements DependencyConsumer {
knownTypes.set(type.index);
String className = type.getName();
- /*
- if (DependencyAnalyzer.shouldLog) {
- System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". "
- + "Target class is " + className);
- }
- */
if (className.startsWith("[")) {
className = "java.lang.Object";
}
diff --git a/core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java b/core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java
index c5b4ad27d..936864d3a 100644
--- a/core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java
+++ b/core/src/main/java/org/teavm/model/ClassHolderTransformerContext.java
@@ -28,4 +28,6 @@ public interface ClassHolderTransformerContext {
boolean isObfuscated();
boolean isStrict();
+
+ void submit(ClassHolder cls);
}
diff --git a/pom.xml b/pom.xml
index 3bbf8fbc6..a3278be96 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,7 @@
11
1.7.11
3.8.0
+ 7.1.0
4.13.2
1.4
@@ -212,6 +213,11 @@
rhino
${rhino.version}
+
+ org.testng
+ testng
+ ${testng.version}
+
commons-cli
commons-cli
diff --git a/tools/junit/pom.xml b/tools/junit/pom.xml
index cbb120627..4b1ba85e3 100644
--- a/tools/junit/pom.xml
+++ b/tools/junit/pom.xml
@@ -35,6 +35,11 @@
junit
provided
+
+ org.testng
+ testng
+ provided
+
org.teavm
teavm-tooling
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 6ffca38a6..8eba20ab4 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TeaVMTestRunner.java
@@ -28,6 +28,7 @@ import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -49,10 +50,6 @@ 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;
@@ -74,11 +71,13 @@ import org.teavm.dependency.PreciseDependencyAnalyzer;
import org.teavm.diagnostics.DefaultProblemTextConsumer;
import org.teavm.diagnostics.Problem;
import org.teavm.model.AnnotationHolder;
+import org.teavm.model.AnnotationReader;
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.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.PreOptimizingClassHolderSource;
import org.teavm.model.ReferenceCache;
@@ -96,8 +95,14 @@ public class TeaVMTestRunner extends Runner implements Filterable {
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_IGNORE = "org.junit.Ignore";
+ static final String TESTNG_TEST = "org.testng.annotations.Test";
+ static final String TESTNG_IGNORE = "org.testng.annotations.Ignore";
static final String JUNIT4_BEFORE = "org.junit.Before";
+ static final String TESTNG_BEFORE = "org.testng.annotations.BeforeMethod";
static final String JUNIT4_AFTER = "org.junit.After";
+ static final String TESTNG_AFTER = "org.testng.annotations.AfterMethod";
+ static final String TESTNG_PROVIDER = "org.testng.annotations.DataProvider";
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.threads";
@@ -335,11 +340,17 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}
private boolean isTestMethod(Method method) {
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return false;
+ }
+
if (TestCase.class.isAssignableFrom(method.getDeclaringClass())) {
return method.getName().startsWith("test") && method.getName().length() > 4
&& Character.isUpperCase(method.getName().charAt(4));
+ } else if (getClassAnnotation(method, TESTNG_TEST) != null) {
+ return method.getName().startsWith("test_");
} else {
- return method.isAnnotationPresent(Test.class);
+ return getAnnotation(method, JUNIT4_TEST) != null || getAnnotation(method, TESTNG_TEST) != null;
}
}
@@ -391,7 +402,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
Description description = describeChild(child);
notifier.fireTestStarted(description);
- if (child.isAnnotationPresent(Ignore.class)) {
+ if (isIgnored(child)) {
notifier.fireTestIgnored(description);
latch.countDown();
return;
@@ -400,23 +411,11 @@ public class TeaVMTestRunner extends Runner implements Filterable {
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 : getExpectedExceptions(methodHolder)) {
- try {
- expectedExceptions.add(Class.forName(exceptionName, false, classLoader));
- } catch (ClassNotFoundException e) {
- notifier.fireTestFailure(new Failure(description, e));
- notifier.fireTestFinished(description);
- latch.countDown();
- return;
- }
- }
-
if (!child.isAnnotationPresent(SkipJVM.class) && !testClass.isAnnotationPresent(SkipJVM.class)) {
ran = true;
- success = runInJvm(child, notifier, expectedExceptions);
+ ClassHolder classHolder = classSource.get(child.getDeclaringClass().getName());
+ MethodHolder methodHolder = classHolder.getMethod(getDescriptor(child));
+ success = runInJvm(child, notifier, getExpectedExceptions(methodHolder));
}
if (success && outputDir != null) {
@@ -459,7 +458,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
File outputPath = getOutputPathForClass();
File outputPathForMethod = getOutputPath(child);
MethodDescriptor descriptor = getDescriptor(child);
- MethodReference reference = new MethodReference(testClass.getName(), descriptor);
+ MethodReference reference = new MethodReference(child.getDeclaringClass().getName(), descriptor);
File testFilePath = getOutputPath(child);
testFilePath.mkdirs();
@@ -467,7 +466,8 @@ public class TeaVMTestRunner extends Runner implements Filterable {
Map properties = new HashMap<>();
for (TeaVMTestConfiguration configuration : getJavaScriptConfigurations()) {
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".js");
- runs.add(createTestRun(testPath, child, RunKind.JAVASCRIPT, reference.toString(), notifier, onSuccess));
+ runs.add(createTestRun(configuration, testPath, child, RunKind.JAVASCRIPT, reference.toString(),
+ notifier, onSuccess));
File htmlPath = getOutputFile(outputPathForMethod, "test", configuration.getSuffix(), false, ".html");
properties.put("SCRIPT", "../" + testPath.getName());
properties.put("IDENTIFIER", reference.toString());
@@ -480,12 +480,14 @@ public class TeaVMTestRunner extends Runner implements Filterable {
for (TeaVMTestConfiguration configuration : getWasmConfigurations()) {
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), false, ".wasm");
- runs.add(createTestRun(testPath, child, RunKind.WASM, reference.toString(), notifier, onSuccess));
+ runs.add(createTestRun(configuration, testPath, child, RunKind.WASM, reference.toString(),
+ notifier, onSuccess));
}
for (TeaVMTestConfiguration configuration : getCConfigurations()) {
File testPath = getOutputFile(outputPath, "classTest", configuration.getSuffix(), true, ".c");
- runs.add(createTestRun(testPath, child, RunKind.C, reference.toString(), notifier, onSuccess));
+ runs.add(createTestRun(configuration, testPath, child, RunKind.C, reference.toString(),
+ notifier, onSuccess));
}
}
@@ -496,7 +498,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
Map properties = new HashMap<>();
for (TeaVMTestConfiguration configuration : getJavaScriptConfigurations()) {
CompileResult compileResult = compileToJs(singleTest(child), "test", configuration, outputPath);
- TestRun run = prepareRun(child, compileResult, notifier, RunKind.JAVASCRIPT, onSuccess);
+ TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.JAVASCRIPT, onSuccess);
if (run != null) {
runs.add(run);
@@ -515,7 +517,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
for (TeaVMTestConfiguration configuration : getCConfigurations()) {
CompileResult compileResult = compileToC(singleTest(child), "test", configuration, outputPath);
- TestRun run = prepareRun(child, compileResult, notifier, RunKind.C, onSuccess);
+ TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.C, onSuccess);
if (run != null) {
runs.add(run);
}
@@ -524,7 +526,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
for (TeaVMTestConfiguration configuration : getWasmConfigurations()) {
CompileResult compileResult = compileToWasm(singleTest(child), "test", configuration,
outputPath);
- TestRun run = prepareRun(child, compileResult, notifier, RunKind.WASM, onSuccess);
+ TestRun run = prepareRun(configuration, child, compileResult, notifier, RunKind.WASM, onSuccess);
if (run != null) {
runs.add(run);
}
@@ -539,119 +541,167 @@ public class TeaVMTestRunner extends Runner implements Filterable {
onSuccess.accept(true);
}
- 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];
+ static String[] getExpectedExceptions(MethodReader method) {
+ AnnotationReader annot = method.getAnnotations().get(JUNIT4_TEST);
+ if (annot != null) {
+ AnnotationValue expected = annot.getValue("expected");
+ if (expected == null) {
+ return new String[0];
+ }
+
+ ValueType result = expected.getJavaClass();
+ return new String[] { ((ValueType.Object) result).getClassName() };
}
- ValueType result = expected.getJavaClass();
- return new String[] { ((ValueType.Object) result).getClassName() };
+ annot = method.getAnnotations().get(TESTNG_TEST);
+ if (annot != null) {
+ AnnotationValue expected = annot.getValue("expectedExceptions");
+ if (expected == null) {
+ return new String[0];
+ }
+
+ List list = expected.getList();
+ String[] result = new String[list.size()];
+ for (int i = 0; i < list.size(); ++i) {
+ result[i] = ((ValueType.Object) list.get(i).getJavaClass()).getClassName();
+ }
+ return result;
+ }
+
+ return new String[0];
}
- private boolean runInJvm(Method child, RunNotifier notifier, Set> expectedExceptions) {
- Description description = describeChild(child);
- Runner runner;
+ private boolean runInJvm(Method testMethod, RunNotifier notifier, String[] expectedExceptions) {
+ Description description = describeChild(testMethod);
Object instance;
try {
- instance = testClass.newInstance();
- } catch (InstantiationException | IllegalAccessException e) {
+ instance = testClass.getConstructor().newInstance();
+ } catch (InstantiationException | IllegalAccessException | NoSuchMethodException e) {
+ notifier.fireTestFailure(new Failure(description, e));
+ return false;
+ } catch (InvocationTargetException e) {
+ notifier.fireTestFailure(new Failure(description, e.getTargetException()));
+ return false;
+ }
+
+ Runner runner;
+ try {
+ runner = prepareJvmRunner(instance, testMethod, expectedExceptions);
+ } catch (Throwable e) {
notifier.fireTestFailure(new Failure(description, e));
return false;
}
- if (!TestCase.class.isAssignableFrom(testClass)) {
- runner = new JUnit4Runner(instance, child);
+
+ try {
+ runner.run(new Object[0]);
+ return true;
+ } catch (Throwable e) {
+ notifier.fireTestFailure(new Failure(description, e));
+ return false;
+ }
+ }
+
+ private Runner prepareJvmRunner(Object instance, Method testMethod, String[] expectedExceptions) throws Throwable {
+ Runner runner;
+ if (TestCase.class.isAssignableFrom(testClass)) {
+ runner = new JUnit3Runner((TestCase) instance, testMethod);
} else {
- runner = new JUnit3Runner(instance);
- ((TestCase) instance).setName(child.getName());
+ runner = new SimpleMethodRunner(instance, testMethod);
}
+ if (expectedExceptions.length > 0) {
+ runner = new WithExpectedExceptionRunner(runner, expectedExceptions);
+ }
+
+ runner = wrapWithBeforeAndAfter(runner, instance);
+ runner = wrapWithDataProvider(runner, instance, testMethod);
+
+ return runner;
+ }
+
+ private Runner wrapWithBeforeAndAfter(Runner runner, Object instance) {
List> classes = new ArrayList<>();
Class> cls = instance.getClass();
while (cls != null) {
classes.add(cls);
cls = cls.getSuperclass();
}
+
+ List afterMethods = new ArrayList<>();
+ for (Class> c : classes) {
+ for (Method method : c.getMethods()) {
+ if (getAnnotation(method, JUNIT4_AFTER) != null || getAnnotation(method, TESTNG_AFTER) != null) {
+ afterMethods.add(method);
+ }
+ }
+ }
+
+ List beforeMethods = new ArrayList<>();
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 (getAnnotation(method, JUNIT4_BEFORE) != null || getAnnotation(method, TESTNG_BEFORE) != null) {
+ beforeMethods.add(method);
}
}
}
+ if (beforeMethods.isEmpty() && afterMethods.isEmpty()) {
+ return runner;
+ }
+
+ return new WithBeforeAndAfterRunner(runner, instance, beforeMethods.toArray(new Method[0]),
+ afterMethods.toArray(new Method[0]));
+ }
+
+ private Runner wrapWithDataProvider(Runner runner, Object instance, Method testMethod) throws Throwable {
+ AnnotationHolder annot = getAnnotation(testMethod, TESTNG_TEST);
+ if (annot == null) {
+ return runner;
+ }
+
+ String providerName = annot.getValue("dataProvider").getString();
+ if (providerName.isEmpty()) {
+ return runner;
+ }
+
+ Method provider = null;
+ for (Method method : testMethod.getDeclaringClass().getDeclaredMethods()) {
+ AnnotationHolder providerAnnot = getAnnotation(method, TESTNG_PROVIDER);
+ if (providerAnnot != null && providerAnnot.getValue("name").getString().equals(providerName)) {
+ provider = method;
+ break;
+ }
+ }
+
+ Object data;
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));
- }
- }
- }
- }
+ provider.setAccessible(true);
+ data = provider.invoke(instance);
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
}
+
+ return new WithDataProviderRunner(runner, data, testMethod.getParameterTypes());
}
interface Runner {
- void run() throws Throwable;
+ void run(Object[] arguments) throws Throwable;
}
- static class JUnit4Runner implements Runner {
+ static class SimpleMethodRunner implements Runner {
Object instance;
- Method child;
+ Method testMethod;
- JUnit4Runner(Object instance, Method child) {
+ SimpleMethodRunner(Object instance, Method testMethod) {
this.instance = instance;
- this.child = child;
+ this.testMethod = testMethod;
}
@Override
- public void run() throws Throwable {
+ public void run(Object[] arguments) throws Throwable {
try {
- child.invoke(instance);
+ testMethod.invoke(instance, arguments);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
@@ -659,20 +709,158 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}
static class JUnit3Runner implements Runner {
- Object instance;
+ TestCase instance;
+ Method testMethod;
- JUnit3Runner(Object instance) {
+ JUnit3Runner(TestCase instance, Method testMethod) {
this.instance = instance;
+ this.testMethod = testMethod;
}
@Override
- public void run() throws Throwable {
- ((TestCase) instance).runBare();
+ public void run(Object[] arguments) throws Throwable {
+ instance.setName(testMethod.getName());
+ instance.runBare();
}
}
- private TestRun prepareRun(Method child, CompileResult result, RunNotifier notifier, RunKind kind,
- Consumer onComplete) {
+ static class WithDataProviderRunner implements Runner {
+ Runner underlyingRunner;
+ Object data;
+ Class>[] types;
+
+ WithDataProviderRunner(Runner underlyingRunner, Object data, Class>[] types) {
+ this.underlyingRunner = underlyingRunner;
+ this.data = data;
+ this.types = types;
+ }
+
+ @Override
+ public void run(Object[] arguments) throws Throwable {
+ if (arguments.length > 0) {
+ throw new IllegalArgumentException("Expected 0 arguments");
+ }
+ if (data instanceof Iterator) {
+ runWithIteratorData((Iterator>) data);
+ } else {
+ runWithArrayData((Object[][]) data);
+ }
+ }
+
+ private void runWithArrayData(Object[][] data) throws Throwable {
+ for (int i = 0; i < data.length; ++i) {
+ runWithDataRow(data[i]);
+ }
+ }
+
+ private void runWithIteratorData(Iterator> data) throws Throwable {
+ while (data.hasNext()) {
+ runWithDataRow((Object[]) data.next());
+ }
+ }
+
+ private void runWithDataRow(Object[] dataRow) throws Throwable {
+ Object[] args = dataRow.clone();
+ for (int j = 0; j < args.length; ++j) {
+ args[j] = convert(args[j], types[j]);
+ }
+ underlyingRunner.run(args);
+ }
+
+ private Object convert(Object value, Class> type) {
+ if (type == byte.class) {
+ value = ((Number) value).byteValue();
+ } else if (type == short.class) {
+ value = ((Number) value).shortValue();
+ } else if (type == int.class) {
+ value = ((Number) value).intValue();
+ } else if (type == long.class) {
+ value = ((Number) value).longValue();
+ } else if (type == float.class) {
+ value = ((Number) value).floatValue();
+ } else if (type == double.class) {
+ value = ((Number) value).doubleValue();
+ }
+ return value;
+ }
+ }
+
+ static class WithExpectedExceptionRunner implements Runner {
+ private Runner underlyingRunner;
+ private String[] expectedExceptions;
+
+ WithExpectedExceptionRunner(Runner underlyingRunner, String[] expectedExceptions) {
+ this.underlyingRunner = underlyingRunner;
+ this.expectedExceptions = expectedExceptions;
+ }
+
+ @Override
+ public void run(Object[] arguments) throws Throwable {
+ boolean caught = false;
+ try {
+ underlyingRunner.run(arguments);
+ } catch (Exception e) {
+ for (String expected : expectedExceptions) {
+ if (isSubtype(e.getClass(), expected)) {
+ caught = true;
+ break;
+ }
+ }
+ if (!caught) {
+ throw e;
+ }
+ }
+ if (!caught) {
+ throw new AssertionError("Expected exception not thrown");
+ }
+ }
+
+ private boolean isSubtype(Class> cls, String superType) {
+ while (cls != Throwable.class) {
+ if (cls.getName().equals(superType)) {
+ return true;
+ }
+ cls = cls.getSuperclass();
+ }
+ return false;
+ }
+ }
+
+ static class WithBeforeAndAfterRunner implements Runner {
+ private Runner underlyingRunner;
+ private Object instance;
+ private Method[] beforeMethods;
+ private Method[] afterMethods;
+
+ WithBeforeAndAfterRunner(Runner underlyingRunner, Object instance, Method[] beforeMethods,
+ Method[] afterMethods) {
+ this.underlyingRunner = underlyingRunner;
+ this.instance = instance;
+ this.beforeMethods = beforeMethods;
+ this.afterMethods = afterMethods;
+ }
+
+ @Override
+ public void run(Object[] arguments) throws Throwable {
+ for (Method method : beforeMethods) {
+ try {
+ method.invoke(instance);
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+ try {
+ underlyingRunner.run(arguments);
+ } finally {
+ for (Method method : afterMethods) {
+ method.invoke(instance);
+ }
+ }
+ }
+ }
+
+ private TestRun prepareRun(TeaVMTestConfiguration> configuration, Method child, CompileResult result,
+ RunNotifier notifier, RunKind kind, Consumer onComplete) {
Description description = describeChild(child);
if (!result.success) {
@@ -682,11 +870,11 @@ public class TeaVMTestRunner extends Runner implements Filterable {
return null;
}
- return createTestRun(result.file, child, kind, null, notifier, onComplete);
+ return createTestRun(configuration, result.file, child, kind, null, notifier, onComplete);
}
- private TestRun createTestRun(File file, Method child, RunKind kind, String argument, RunNotifier notifier,
- Consumer onComplete) {
+ private TestRun createTestRun(TeaVMTestConfiguration> configuration, File file, Method child, RunKind kind,
+ String argument, RunNotifier notifier, Consumer onComplete) {
Description description = describeChild(child);
TestRunCallback callback = new TestRunCallback() {
@@ -702,8 +890,16 @@ public class TeaVMTestRunner extends Runner implements Filterable {
}
};
- return new TestRun(file.getParentFile(), child, description, file.getName(), kind,
- argument, callback);
+ return new TestRun(generateName(child.getName(), configuration), file.getParentFile(), child, description,
+ file.getName(), kind, argument, callback);
+ }
+
+ private String generateName(String baseName, TeaVMTestConfiguration> configuration) {
+ String suffix = configuration.getSuffix();
+ if (!suffix.isEmpty()) {
+ baseName = baseName + " (" + suffix + ")";
+ }
+ return baseName;
}
private Failure createFailure(Description description, CompileResult result) {
@@ -854,6 +1050,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
vm.setProperties(properties);
List methodReferences = new ArrayList<>();
for (Method method : methods) {
+ if (isIgnored(method)) {
+ continue;
+ }
ClassHolder classHolder = classSource.get(method.getDeclaringClass().getName());
MethodHolder methodHolder = classHolder.getMethod(getDescriptor(method));
methodReferences.add(methodHolder.getReference());
@@ -862,6 +1061,31 @@ public class TeaVMTestRunner extends Runner implements Filterable {
};
}
+ private boolean isIgnored(Method method) {
+ return getAnnotation(method, JUNIT4_IGNORE) != null || getAnnotation(method, TESTNG_IGNORE) != null;
+ }
+
+ private AnnotationHolder getAnnotation(Method method, String name) {
+ ClassHolder cls = classSource.get(method.getDeclaringClass().getName());
+ if (cls == null) {
+ return null;
+ }
+ MethodDescriptor descriptor = getDescriptor(method);
+ MethodHolder methodHolder = cls.getMethod(descriptor);
+ if (methodHolder == null) {
+ return null;
+ }
+ return methodHolder.getAnnotations().get(name);
+ }
+
+ private AnnotationHolder getClassAnnotation(Method method, String name) {
+ ClassHolder cls = classSource.get(method.getDeclaringClass().getName());
+ if (cls == null) {
+ return null;
+ }
+ return cls.getAnnotations().get(name);
+ }
+
private CompileResult compile(TeaVMTestConfiguration configuration,
Supplier targetSupplier, String entryPoint, File path, String extension,
CompilePostProcessor postBuild, boolean separateDir,
@@ -1096,7 +1320,7 @@ public class TeaVMTestRunner extends Runner implements Filterable {
Writer writer = new OutputStreamWriter(bufferedOutput)) {
writer.write("[\n");
boolean first = true;
- for (TestRun run : runsInCurrentClass) {
+ for (TestRun run : runsInCurrentClass.toArray(new TestRun[0])) {
if (!first) {
writer.write(",\n");
}
@@ -1114,6 +1338,9 @@ public class TeaVMTestRunner extends Runner implements Filterable {
writer.write(" \"argument\": ");
writeJsonString(writer, run.getArgument());
}
+ writer.write(",\n");
+ writer.write(" \"name\": ");
+ writeJsonString(writer, run.getName());
writer.write("\n }");
}
writer.write("\n]");
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 8187aa05b..6243594e9 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPoint.java
@@ -15,32 +15,46 @@
*/
package org.teavm.junit;
+import java.util.ArrayList;
+import java.util.List;
+
final class TestEntryPoint {
private static Object testCase;
private TestEntryPoint() {
}
- public static void run(String name) throws Exception {
- before();
- try {
- launchTest(name);
- } finally {
+ public static void run(String name) throws Throwable {
+ List launchers = new ArrayList<>();
+ testCase = createTestCase();
+ launchers(name, launchers);
+ for (Launcher launcher : launchers) {
+ before();
try {
- after();
- } catch (Throwable e) {
- e.printStackTrace();
+ launcher.launch(testCase);
+ } finally {
+ try {
+ after();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
}
}
}
+ private static native Object createTestCase();
+
private static native void before();
- private static native void launchTest(String name) throws Exception;
+ private static native void launchers(String name, List result) throws Throwable;
private static native void after();
public static void main(String[] args) throws Throwable {
run(args.length == 1 ? args[0] : null);
}
+
+ interface Launcher {
+ void launch(Object testCase) throws Throwable;
+ }
}
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 326010ae9..6ed01da7f 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformer.java
@@ -20,7 +20,10 @@ 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 static org.teavm.junit.TeaVMTestRunner.TESTNG_AFTER;
+import static org.teavm.junit.TeaVMTestRunner.TESTNG_BEFORE;
+import static org.teavm.junit.TeaVMTestRunner.TESTNG_PROVIDER;
+import static org.teavm.junit.TeaVMTestRunner.TESTNG_TEST;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -34,12 +37,14 @@ import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
+import org.teavm.model.FieldHolder;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.TryCatchBlock;
import org.teavm.model.ValueType;
+import org.teavm.model.emit.PhiEmitter;
import org.teavm.model.emit.ProgramEmitter;
import org.teavm.model.emit.ValueEmitter;
import org.teavm.vm.spi.TeaVMHost;
@@ -47,6 +52,7 @@ import org.teavm.vm.spi.TeaVMPlugin;
abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaVMPlugin {
private String testClassName;
+ private int suffixGenerator;
TestEntryPointTransformer(String testClassName) {
this.testClassName = testClassName;
@@ -60,18 +66,23 @@ abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaV
@Override
public void transformClass(ClassHolder cls, ClassHolderTransformerContext context) {
if (cls.getName().equals(TestEntryPoint.class.getName())) {
+ suffixGenerator = 0;
for (MethodHolder method : cls.getMethods()) {
switch (method.getName()) {
- case "launchTest":
- method.setProgram(generateLaunchProgram(method, context.getHierarchy()));
+ case "createTestCase":
+ generateCreateTestCaseProgram(method, context.getHierarchy());
+ method.getModifiers().remove(ElementModifier.NATIVE);
+ break;
+ case "launchers":
+ generateLaunchProgram(method, context);
method.getModifiers().remove(ElementModifier.NATIVE);
break;
case "before":
- method.setProgram(generateBeforeProgram(method, context.getHierarchy()));
+ generateBeforeProgram(method, context.getHierarchy());
method.getModifiers().remove(ElementModifier.NATIVE);
break;
case "after":
- method.setProgram(generateAfterProgram(method, context.getHierarchy()));
+ generateAfterProgram(method, context.getHierarchy());
method.getModifiers().remove(ElementModifier.NATIVE);
break;
}
@@ -79,14 +90,13 @@ abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaV
}
}
- private Program generateBeforeProgram(MethodHolder method, ClassHierarchy hierarchy) {
+ private void generateCreateTestCaseProgram(MethodHolder method, ClassHierarchy hierarchy) {
+ ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
+ pe.construct(testClassName).cast(Object.class).returnValue();
+ }
+
+ private void generateBeforeProgram(MethodHolder method, ClassHierarchy hierarchy) {
ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
- 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 (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testClassName, false)) {
@@ -97,11 +107,11 @@ abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaV
Collections.reverse(classes);
classes.stream()
.flatMap(cls -> cls.getMethods().stream())
- .filter(m -> m.getAnnotations().get(JUNIT4_BEFORE) != null)
+ .filter(m -> m.getAnnotations().get(JUNIT4_BEFORE) != null
+ || m.getAnnotations().get(TESTNG_BEFORE) != null)
.forEach(m -> testCaseVar.cast(ValueType.object(m.getOwnerName())).invokeVirtual(m.getReference()));
pe.exit();
- return pe.getProgram();
}
private Program generateAfterProgram(MethodHolder method, ClassHierarchy hierarchy) {
@@ -111,7 +121,8 @@ abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaV
List classes = collectSuperClasses(pe.getClassSource(), testClassName);
classes.stream()
.flatMap(cls -> cls.getMethods().stream())
- .filter(m -> m.getAnnotations().get(JUNIT4_AFTER) != null)
+ .filter(m -> m.getAnnotations().get(JUNIT4_AFTER) != null
+ || m.getAnnotations().get(TESTNG_AFTER) != null)
.forEach(m -> testCaseVar.cast(ValueType.object(m.getOwnerName())).invokeVirtual(m.getReference()));
if (hierarchy.isSuperType(JUNIT3_BASE_CLASS, testClassName, false)) {
@@ -135,23 +146,181 @@ abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaV
return result;
}
- protected abstract Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy);
+ protected abstract void generateLaunchProgram(MethodHolder method, ClassHolderTransformerContext context);
protected final void generateSingleMethodLaunchProgram(MethodReference testMethod,
- ClassHierarchy hierarchy, ProgramEmitter pe) {
- pe.getField(TestEntryPoint.class, "testCase", Object.class)
- .cast(ValueType.object(testMethod.getClassName()))
- .invokeSpecial(testMethod);
+ ClassHolderTransformerContext context, ProgramEmitter pe) {
+ ClassHolder launcherClass = generateLauncherClass(testMethod, context.getHierarchy());
+ context.submit(launcherClass);
+ ValueEmitter list = pe.var(2, List.class);
+
+ MethodReader testMethodReader = context.getHierarchy().getClassSource().resolve(testMethod);
+ AnnotationReader testNgAnnot = testMethodReader.getAnnotations().get(TESTNG_TEST);
+ if (testNgAnnot != null) {
+ AnnotationValue dataProviderValue = testNgAnnot.getValue("dataProvider");
+ if (dataProviderValue != null) {
+ generateAddLaunchersWithProvider(testMethodReader, context.getHierarchy(), pe, list,
+ dataProviderValue.getString(), launcherClass.getName());
+ return;
+ }
+ }
+
+ list.invokeVirtual("add", boolean.class, pe.construct(launcherClass.getName()).cast(Object.class));
+ pe.exit();
+ }
+
+ private void generateAddLaunchersWithProvider(MethodReader testMethodReader, ClassHierarchy hierarchy,
+ ProgramEmitter pe, ValueEmitter list, String providerName, String launcherClassName) {
+ ClassReader owningClass = hierarchy.getClassSource().get(testMethodReader.getOwnerName());
+ MethodReader providerMethod = null;
+ for (MethodReader method : owningClass.getMethods()) {
+ AnnotationReader annot = method.getAnnotations().get(TESTNG_PROVIDER);
+ if (annot != null && annot.getValue("name").getString().equals(providerName)) {
+ providerMethod = method;
+ break;
+ }
+ }
+
+ ValueEmitter data = pe.getField(TestEntryPoint.class, "testCase", Object.class)
+ .cast(ValueType.object(testMethodReader.getOwnerName()))
+ .invokeSpecial(providerMethod.getReference());
+ if (data.getType() instanceof ValueType.Array) {
+ generateAddLaunchersWithProviderArray(testMethodReader, pe, list, data, launcherClassName);
+ } else {
+ generateAddLaunchersWithProviderIterator(testMethodReader, pe, list, data, launcherClassName);
+ }
+ }
+
+ private void generateAddLaunchersWithProviderArray(MethodReader testMethodReader, ProgramEmitter pe,
+ ValueEmitter list, ValueEmitter data, String launcherClassName) {
+ ValueEmitter size = data.arrayLength();
+ BasicBlock loopHead = pe.getProgram().createBasicBlock();
+ BasicBlock loopBody = pe.getProgram().createBasicBlock();
+ BasicBlock loopExit = pe.getProgram().createBasicBlock();
+ PhiEmitter index = pe.phi(int.class, loopHead);
+ pe.constant(0).propagateTo(index);
+ pe.jump(loopHead);
+
+ pe.enter(loopHead);
+ pe.when(index.getValue().isLessThan(size))
+ .thenDo(() -> pe.jump(loopBody))
+ .elseDo(() -> pe.jump(loopExit));
+
+ pe.enter(loopBody);
+ ValueEmitter dataRow = data.getElement(index.getValue());
+ generateAddLauncherWithData(testMethodReader, pe, list, dataRow, launcherClassName);
+ index.getValue().add(1).propagateTo(index);
+ pe.jump(loopHead);
+
+ pe.enter(loopExit);
+ pe.exit();
+ }
+
+ private void generateAddLaunchersWithProviderIterator(MethodReader testMethodReader, ProgramEmitter pe,
+ ValueEmitter list, ValueEmitter data, String launcherClassName) {
+ BasicBlock loopHead = pe.getProgram().createBasicBlock();
+ BasicBlock loopBody = pe.getProgram().createBasicBlock();
+ BasicBlock loopExit = pe.getProgram().createBasicBlock();
+ pe.jump(loopHead);
+
+ pe.enter(loopHead);
+ pe.when(data.invokeVirtual("hasNext", boolean.class).isTrue())
+ .thenDo(() -> pe.jump(loopBody))
+ .elseDo(() -> pe.jump(loopExit));
+
+ pe.enter(loopBody);
+ ValueEmitter dataRow = data.invokeVirtual("next", Object.class).cast(Object[].class);
+ generateAddLauncherWithData(testMethodReader, pe, list, dataRow, launcherClassName);
+ pe.jump(loopHead);
+
+ pe.enter(loopExit);
+ pe.exit();
+ }
+
+ private void generateAddLauncherWithData(MethodReader testMethodReader, ProgramEmitter pe, ValueEmitter list,
+ ValueEmitter dataRow, String launcherClassName) {
+ List arguments = new ArrayList<>();
+ for (int i = 0; i < testMethodReader.parameterCount(); ++i) {
+ ValueType type = testMethodReader.parameterType(i);
+ arguments.add(convertArgument(dataRow.getElement(i), type));
+ }
+
+ list.invokeVirtual("add", boolean.class, pe.construct(launcherClassName,
+ arguments.toArray(new ValueEmitter[0])).cast(Object.class));
+ }
+
+ private ValueEmitter convertArgument(ValueEmitter value, ValueType type) {
+ if (type instanceof ValueType.Primitive) {
+ switch (((ValueType.Primitive) type).getKind()) {
+ case BOOLEAN:
+ return value.cast(Boolean.class).invokeVirtual("booleanValue", boolean.class);
+ case CHARACTER:
+ return value.cast(Character.class).invokeVirtual("charValue", char.class);
+ case BYTE:
+ return value.cast(Number.class).invokeVirtual("byteValue", byte.class);
+ case SHORT:
+ return value.cast(Number.class).invokeVirtual("shortValue", byte.class);
+ case INTEGER:
+ return value.cast(Number.class).invokeVirtual("intValue", int.class);
+ case LONG:
+ return value.cast(Number.class).invokeVirtual("longValue", long.class);
+ case FLOAT:
+ return value.cast(Number.class).invokeVirtual("floatValue", float.class);
+ case DOUBLE:
+ return value.cast(Number.class).invokeVirtual("doubleValue", double.class);
+ }
+ }
+ return value.cast(type);
+ }
+
+ private ClassHolder generateLauncherClass(MethodReference testMethod, ClassHierarchy hierarchy) {
+ ClassHolder cls = new ClassHolder(TestEntryPoint.Launcher.class.getName() + "Impl" + suffixGenerator++);
+ cls.setParent("java.lang.Object");
+ cls.getInterfaces().add(TestEntryPoint.Launcher.class.getName());
+
+ MethodHolder constructor = new MethodHolder("", testMethod.getSignature());
+ cls.addMethod(constructor);
+ ProgramEmitter pe = ProgramEmitter.create(constructor, hierarchy);
+ pe.invoke(Object.class, "", void.class);
+ ValueEmitter self = pe.var(0, ValueType.object(cls.getName()));
+ for (int i = 0; i < testMethod.parameterCount(); ++i) {
+ FieldHolder paramField = new FieldHolder("param_" + i);
+ paramField.setType(testMethod.parameterType(i));
+ cls.addField(paramField);
+ self.setField(paramField.getName(), pe.var(i + 1, testMethod.parameterType(i)));
+ }
+ pe.exit();
+
+ MethodHolder launchMethod = new MethodHolder("launch", ValueType.parse(Object.class), ValueType.VOID);
+ cls.addMethod(launchMethod);
+ pe = ProgramEmitter.create(launchMethod, hierarchy);
+ List arguments = new ArrayList<>();
+ self = pe.var(0, ValueType.object(cls.getName()));
+ for (int i = 0; i < testMethod.parameterCount(); ++i) {
+ arguments.add(self.getField("param_" + i, testMethod.parameterType(i)));
+ }
+ generateRunMethodOnce(testMethod, hierarchy, pe, pe.var(1, Object.class), arguments);
+ pe.exit();
+
+ return cls;
+ }
+
+ private void generateRunMethodOnce(MethodReference testMethod, ClassHierarchy hierarchy, ProgramEmitter pe,
+ ValueEmitter testCase, List arguments) {
+ testCase.cast(ValueType.object(testMethod.getClassName()))
+ .invokeSpecial(testMethod, arguments.toArray(new ValueEmitter[0]));
MethodReader testMethodReader = hierarchy.getClassSource().resolve(testMethod);
- AnnotationReader testAnnotation = testMethodReader.getAnnotations().get(JUNIT4_TEST);
- AnnotationValue throwsValue = testAnnotation != null ? testAnnotation.getValue("expected") : null;
- if (throwsValue != null) {
+ String[] expectedExceptions = TeaVMTestRunner.getExpectedExceptions(testMethodReader);
+ if (expectedExceptions.length != 0) {
BasicBlock handler = pe.getProgram().createBasicBlock();
- TryCatchBlock tryCatch = new TryCatchBlock();
- tryCatch.setExceptionType(((ValueType.Object) throwsValue.getJavaClass()).getClassName());
- tryCatch.setHandler(handler);
- pe.getBlock().getTryCatchBlocks().add(tryCatch);
+
+ for (String exceptionType : expectedExceptions) {
+ TryCatchBlock tryCatch = new TryCatchBlock();
+ tryCatch.setExceptionType(exceptionType);
+ tryCatch.setHandler(handler);
+ pe.getBlock().getTryCatchBlocks().add(tryCatch);
+ }
BasicBlock nextBlock = pe.getProgram().createBasicBlock();
pe.jump(nextBlock);
@@ -159,9 +328,6 @@ abstract class TestEntryPointTransformer implements ClassHolderTransformer, TeaV
pe.construct(AssertionError.class, pe.constant("Expected exception not thrown")).raise();
pe.enter(handler);
- pe.exit();
- } else {
- pe.exit();
}
}
}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForSingleMethod.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForSingleMethod.java
index 713ee8cd3..796dabd7f 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForSingleMethod.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForSingleMethod.java
@@ -15,10 +15,9 @@
*/
package org.teavm.junit;
-import org.teavm.model.ClassHierarchy;
+import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
-import org.teavm.model.Program;
import org.teavm.model.emit.ProgramEmitter;
class TestEntryPointTransformerForSingleMethod extends TestEntryPointTransformer {
@@ -30,9 +29,8 @@ class TestEntryPointTransformerForSingleMethod extends TestEntryPointTransformer
}
@Override
- protected Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy) {
- ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
- generateSingleMethodLaunchProgram(testMethod, hierarchy, pe);
- return pe.getProgram();
+ protected void generateLaunchProgram(MethodHolder method, ClassHolderTransformerContext context) {
+ ProgramEmitter pe = ProgramEmitter.create(method, context.getHierarchy());
+ generateSingleMethodLaunchProgram(testMethod, context, pe);
}
}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForWholeClass.java b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForWholeClass.java
index 4e4d6665b..2ea15c1e7 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForWholeClass.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TestEntryPointTransformerForWholeClass.java
@@ -16,10 +16,9 @@
package org.teavm.junit;
import java.util.List;
-import org.teavm.model.ClassHierarchy;
+import org.teavm.model.ClassHolderTransformerContext;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
-import org.teavm.model.Program;
import org.teavm.model.emit.ForkEmitter;
import org.teavm.model.emit.ProgramEmitter;
import org.teavm.model.emit.ValueEmitter;
@@ -34,8 +33,8 @@ class TestEntryPointTransformerForWholeClass extends TestEntryPointTransformer {
}
@Override
- protected Program generateLaunchProgram(MethodHolder method, ClassHierarchy hierarchy) {
- ProgramEmitter pe = ProgramEmitter.create(method, hierarchy);
+ protected void generateLaunchProgram(MethodHolder method, ClassHolderTransformerContext context) {
+ ProgramEmitter pe = ProgramEmitter.create(method, context.getHierarchy());
ValueEmitter testName = pe.var(1, String.class);
for (MethodReference testMethod : testMethods) {
@@ -45,13 +44,11 @@ class TestEntryPointTransformerForWholeClass extends TestEntryPointTransformer {
pe.enter(pe.getProgram().createBasicBlock());
fork.setThen(pe.getBlock());
- generateSingleMethodLaunchProgram(testMethod, hierarchy, pe);
+ generateSingleMethodLaunchProgram(testMethod, context, pe);
pe.enter(pe.getProgram().createBasicBlock());
fork.setElse(pe.getBlock());
}
pe.construct(IllegalArgumentException.class, pe.constant("Invalid test name")).raise();
-
- return pe.getProgram();
}
}
diff --git a/tools/junit/src/main/java/org/teavm/junit/TestRun.java b/tools/junit/src/main/java/org/teavm/junit/TestRun.java
index fb8f4dfd8..e7f460dc6 100644
--- a/tools/junit/src/main/java/org/teavm/junit/TestRun.java
+++ b/tools/junit/src/main/java/org/teavm/junit/TestRun.java
@@ -20,6 +20,7 @@ import java.lang.reflect.Method;
import org.junit.runner.Description;
class TestRun {
+ private String name;
private File baseDirectory;
private Method method;
private Description description;
@@ -28,8 +29,9 @@ class TestRun {
private TestRunCallback callback;
private String argument;
- TestRun(File baseDirectory, Method method, Description description, String fileName, RunKind kind,
+ TestRun(String name, File baseDirectory, Method method, Description description, String fileName, RunKind kind,
String argument, TestRunCallback callback) {
+ this.name = name;
this.baseDirectory = baseDirectory;
this.method = method;
this.description = description;
@@ -39,6 +41,10 @@ class TestRun {
this.callback = callback;
}
+ public String getName() {
+ return name;
+ }
+
public File getBaseDirectory() {
return baseDirectory;
}