Skip to content

Commit e6a0382

Browse files
committed
testing JUnit 5 equivalent of XpectRunner
1 parent e27f2f2 commit e6a0382

File tree

14 files changed

+598
-12
lines changed

14 files changed

+598
-12
lines changed

org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectFileAccess.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.eclipse.xpect.XpectFile;
3131
import org.eclipse.xpect.registry.ILanguageInfo;
3232
import org.eclipse.xpect.runner.XpectRunner;
33+
import org.eclipse.xpect.runner.XpectTestGlobalState;
3334

3435
import com.google.inject.Injector;
3536

@@ -67,8 +68,8 @@ protected static ResourceSet cloneResourceSet(ResourceSet rs) {
6768
// need delegation or nothing because of "java" protocol
6869
// result.setResourceFactoryRegistry(rs.getResourceFactoryRegistry());
6970
result.setURIConverter(rs.getURIConverter());
70-
if (XpectRunner.testClassloader != null) {
71-
result.setClasspathURIContext(XpectRunner.testClassloader);
71+
if (XpectTestGlobalState.INSTANCE.testClass() != null) {
72+
result.setClasspathURIContext(XpectTestGlobalState.INSTANCE.testClass().getClassLoader());
7273
result.setClasspathUriResolver(new ClassloaderClasspathUriResolver());
7374
} else if (rs instanceof XtextResourceSet) {
7475
XtextResourceSet xrs = (XtextResourceSet) rs;

org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectUtil.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.eclipse.xpect.XpectFile;
2929
import org.eclipse.xpect.XpectJavaModel;
3030
import org.eclipse.xpect.runner.XpectRunner;
31+
import org.eclipse.xpect.runner.XpectTestGlobalState;
3132
import org.eclipse.xpect.ui.internal.XpectActivator;
3233

3334
import com.google.inject.Injector;
@@ -40,8 +41,8 @@ public static XpectFile loadFile(IFile file) {
4041
Injector injector = XpectActivator.getInstance().getInjector(XpectActivator.ORG_ECLIPSE_XPECT_XPECT);
4142
XtextResourceSet rs = new XtextResourceSet();
4243
IJavaProject javaProject = JavaCore.create(file.getProject());
43-
if (XpectRunner.testClassloader != null) {
44-
rs.setClasspathURIContext(XpectRunner.testClassloader);
44+
if (XpectTestGlobalState.INSTANCE.testClass() != null) {
45+
rs.setClasspathURIContext(XpectTestGlobalState.INSTANCE.testClass().getClassLoader());
4546
rs.setClasspathUriResolver(new ClassloaderClasspathUriResolver());
4647
} else if (javaProject != null && javaProject.exists()) {
4748
rs.setClasspathURIContext(javaProject);

org.eclipse.xpect/META-INF/MANIFEST.MF

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ Require-Bundle: org.eclipse.xtext,
1919
org.antlr.runtime;bundle-version="[3.2.0,3.2.1)",
2020
org.apache.log4j;bundle-version="1.2.24",
2121
org.junit;bundle-version="4.11.0";visibility:=reexport,
22+
junit-jupiter-api;bundle-version="5.10.2";visibility:=reexport,
2223
org.eclipse.xtext.common.types;visibility:=reexport,
2324
org.apache.log4j;bundle-version="1.2.0";visibility:=reexport,
2425
org.objectweb.asm;bundle-version="[9.5.0,10.0.0)";resolution:=optional
2526
Bundle-RequiredExecutionEnvironment: JavaSE-11
2627
Export-Package: org.eclipse.xpect,
28+
org.eclipse.xpect.dynamic,
2729
org.eclipse.xpect.expectation,
2830
org.eclipse.xpect.expectation.impl;
2931
x-friends:="org.eclipse.xpect.xtext.lib,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.eclipse.xpect.dynamic;
2+
3+
import java.util.stream.Stream;
4+
5+
import org.eclipse.xpect.XpectImport;
6+
import org.eclipse.xpect.runner.TestTitleProvider;
7+
import org.junit.jupiter.api.DynamicContainer;
8+
import org.junit.jupiter.api.TestFactory;
9+
10+
@XpectImport(TestTitleProvider.class)
11+
public interface IXpectDynamicTestFactory {
12+
13+
@TestFactory
14+
default Stream<DynamicContainer> generateTests() {
15+
return XpectDynamicTestFactory.xpectTests(getClass());
16+
}
17+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package org.eclipse.xpect.dynamic;
2+
3+
import java.lang.reflect.InvocationTargetException;
4+
5+
import org.eclipse.xpect.XjmTestMethod;
6+
import org.eclipse.xpect.runner.ValidatingSetup;
7+
import org.eclipse.xpect.runner.XpectTestGlobalState;
8+
import org.eclipse.xpect.setup.ThisTestObject;
9+
import org.eclipse.xpect.state.Creates;
10+
import org.eclipse.xpect.state.StateContainer;
11+
import org.junit.jupiter.api.DynamicTest;
12+
13+
import com.google.common.base.Preconditions;
14+
15+
public class XpectDynamicTest {
16+
17+
private final String className;
18+
private XjmTestMethod method;
19+
private final StateContainer state;
20+
21+
public XpectDynamicTest(StateContainer state, XjmTestMethod method) {
22+
Preconditions.checkNotNull(method);
23+
this.className = XpectTestGlobalState.INSTANCE.testClass().getName();
24+
this.method = method;
25+
this.state = state;
26+
}
27+
28+
@Creates
29+
public XpectDynamicTest create() {
30+
return this;
31+
}
32+
33+
public XjmTestMethod getMethod() {
34+
return method;
35+
}
36+
37+
public StateContainer getState() {
38+
return state;
39+
}
40+
41+
public DynamicTest test() {
42+
String testName = getName();
43+
return DynamicTest.dynamicTest(testName, () -> runInternal());
44+
}
45+
46+
public String getName() {
47+
String testName = formatDisplayName(method.getName(), className);
48+
return testName;
49+
}
50+
51+
protected void runInternal() throws Throwable {
52+
Object test = state.get(Object.class, ThisTestObject.class).get();
53+
try {
54+
method.getJavaMethod().invoke(test);
55+
} catch (InvocationTargetException e) {
56+
throw e.getCause();
57+
}
58+
}
59+
60+
private static String formatDisplayName(String name, String className) {
61+
return String.format("%s(%s)", name, className);
62+
}
63+
64+
}
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package org.eclipse.xpect.dynamic;
2+
3+
import java.util.Collection;
4+
import java.util.List;
5+
import java.util.concurrent.atomic.AtomicBoolean;
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
8+
import org.eclipse.emf.common.util.URI;
9+
import org.eclipse.xpect.XjmMethod;
10+
import org.eclipse.xpect.XjmTestMethod;
11+
import org.eclipse.xpect.XpectFile;
12+
import org.eclipse.xpect.XpectInvocation;
13+
import org.eclipse.xpect.XpectJavaModel;
14+
import org.eclipse.xpect.runner.IXpectURIProvider;
15+
import org.eclipse.xpect.runner.TestExecutor;
16+
import org.eclipse.xpect.runner.ValidatingSetup;
17+
import org.eclipse.xpect.setup.ThisRootTestClass;
18+
import org.eclipse.xpect.state.Creates;
19+
import org.eclipse.xpect.state.StateContainer;
20+
import org.junit.jupiter.api.DynamicContainer;
21+
import org.junit.jupiter.api.DynamicTest;
22+
23+
import com.google.common.collect.Iterables;
24+
import com.google.common.collect.Lists;
25+
26+
public class XpectDynamicTestCase {
27+
private List<DynamicTest> children;
28+
private final StateContainer state;
29+
private final XpectFile xpectFile;
30+
31+
public XpectDynamicTestCase(StateContainer state, XpectFile file) {
32+
this.xpectFile = file;
33+
this.state = state;
34+
}
35+
36+
@Creates
37+
public XpectDynamicTestCase create() {
38+
return this;
39+
}
40+
41+
/**
42+
* <p>
43+
* To run code before an Xpect test begins, extend this class and annotate the extending class with:
44+
* {@code @org.eclipse.xpect.XpectReplace(org.eclipse.xpect.dynamic.XpectDynamicTestCase)}
45+
* </p>
46+
* <p>
47+
* The extended class must then be included in the values of the annotation of the test case:
48+
* {@code @org.eclipse.xpect.XpectImport({...class})}
49+
* </p>
50+
*/
51+
public void setUp() throws Throwable {
52+
// nothing to set up
53+
}
54+
55+
/**
56+
* <p>
57+
* To run code after an Xpect test finishes, extend this class and annotate the extending class with:
58+
* {@code @org.eclipse.xpect.XpectReplace(org.eclipse.xpect.dynamic.XpectDynamicTestCase)}
59+
* </p>
60+
* <p>
61+
* The extended class must then be included in the values of the annotation of the test case:
62+
* {@code @org.eclipse.xpect.XpectImport({...class})}
63+
* </p>
64+
*/
65+
public void tearDown() throws Throwable {
66+
// nothing to tear down
67+
}
68+
69+
public DynamicContainer dynamicContainer() {
70+
return DynamicContainer.dynamicContainer(getName(), getChildren());
71+
}
72+
73+
protected List<DynamicTest> createChildren() {
74+
List<DynamicTest> children = Lists.newArrayList();
75+
if (xpectFile != null) {
76+
/*
77+
* With JUnit 4 runners, we can do setup validation before children run and state clean-up after children are done.
78+
* With JUnit 5 we cannot.
79+
* So the first test does setup validation and the last test does clean-up.
80+
* Meaning their execution times will likely increase notably, when compared to the JUnit 4 reported time.
81+
* Alternatively we can add a first and last test, for setup validation resp. clean-up. This means extra test nodes in the result, as well as extra test results. We then also have the problem of running tests if the setup validation failed.
82+
* XXX: does JUnit 5 offer anything for dynamic tests, to improve this situation?
83+
* As of writing this code, @BeforeEach and @AfterEach are only called before and after the factory method runs.
84+
* We have a factory method with every URL we must run an Xpect test for, as those URLs are known only via an annotation.
85+
*/
86+
AtomicBoolean setupValidated = new AtomicBoolean(false);
87+
AtomicBoolean setupValidationFailed = new AtomicBoolean(false);
88+
AtomicInteger finishedChildren = new AtomicInteger(0);
89+
XpectJavaModel xjm = xpectFile.getJavaModel();
90+
XjmTestMethod[] methods = xjm.getMethods().values().stream().filter(m -> m instanceof XjmTestMethod).toArray(XjmTestMethod[]::new);
91+
XpectInvocation[] invocations = Iterables.toArray(xpectFile.getInvocations(), XpectInvocation.class);
92+
int childrenCount = methods.length + invocations.length;
93+
for (XjmTestMethod method : methods) {
94+
DynamicTest test = createDynamicTest(method);
95+
children.add(wrapTest(test, childrenCount, setupValidated, setupValidationFailed, finishedChildren));
96+
}
97+
for (XpectInvocation inv : invocations) {
98+
DynamicTest test = createDynamicTest(inv);
99+
children.add(wrapTest(test, childrenCount, setupValidated, setupValidationFailed, finishedChildren));
100+
}
101+
}
102+
return children;
103+
}
104+
105+
protected DynamicTest createDynamicTest(XjmTestMethod method) {
106+
StateContainer childState = TestExecutor.createState(state, TestExecutor.createTestConfiguration(method));
107+
return childState.get(XpectDynamicTest.class).get().test();
108+
}
109+
110+
protected DynamicTest createDynamicTest(XpectInvocation invocation) {
111+
StateContainer childState = TestExecutor.createState(state, TestExecutor.createXpectConfiguration(invocation));
112+
DynamicTest test = childState.get(XpectInvocationDynamicTest.class).get().test();
113+
return test;
114+
}
115+
116+
protected DynamicTest wrapTest(DynamicTest test, final int childrenCount, AtomicBoolean validatedSetup, AtomicBoolean setupValidationFailed, AtomicInteger finishedChildren) {
117+
return DynamicTest.dynamicTest(test.getDisplayName(), () -> {
118+
try {
119+
if (!validatedSetup.getAndSet(true)) {
120+
// first test is running, validate setup
121+
try {
122+
state.get(ValidatingSetup.class).get().validate();
123+
} catch (Throwable t) {
124+
setupValidationFailed.set(true);
125+
throw t;
126+
}
127+
}
128+
if (setupValidationFailed.get()) {
129+
throw new AssertionError("Setup validation failed");
130+
}
131+
try {
132+
setUp();
133+
test.getExecutable().execute();
134+
} finally {
135+
tearDown();
136+
}
137+
} finally {
138+
int finished = finishedChildren.incrementAndGet();
139+
if (finished >= childrenCount) {
140+
// last test is done, do clean-up
141+
state.invalidate();
142+
}
143+
}
144+
});
145+
}
146+
147+
protected List<DynamicTest> getChildren() {
148+
if (children == null)
149+
children = createChildren();
150+
return children;
151+
}
152+
153+
public Class<?> getJavaTestClass() {
154+
return state.get(Class.class, ThisRootTestClass.class).get();
155+
}
156+
157+
public IXpectURIProvider getURIProvider() {
158+
return state.get(IXpectURIProvider.class).get();
159+
}
160+
161+
public StateContainer getState() {
162+
return state;
163+
}
164+
165+
public URI getUri() {
166+
return xpectFile.eResource().getURI();
167+
}
168+
169+
public XpectFile getXpectFile() {
170+
return xpectFile;
171+
}
172+
173+
public String getName() {
174+
IXpectURIProvider uriProvider = getURIProvider();
175+
URI uri = getUri();
176+
URI deresolved = uriProvider.deresolveToProject(uri);
177+
String pathInProject = deresolved.trimSegments(1).toString();
178+
String fileName = deresolved.lastSegment();
179+
return fileName + ": " + pathInProject;
180+
}
181+
}

0 commit comments

Comments
 (0)