diff --git a/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectFileAccess.java b/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectFileAccess.java index 2edf2a91..5cfff814 100644 --- a/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectFileAccess.java +++ b/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectFileAccess.java @@ -30,6 +30,7 @@ import org.eclipse.xpect.XpectFile; import org.eclipse.xpect.registry.ILanguageInfo; import org.eclipse.xpect.runner.XpectRunner; +import org.eclipse.xpect.runner.XpectTestGlobalState; import com.google.inject.Injector; @@ -67,8 +68,8 @@ protected static ResourceSet cloneResourceSet(ResourceSet rs) { // need delegation or nothing because of "java" protocol // result.setResourceFactoryRegistry(rs.getResourceFactoryRegistry()); result.setURIConverter(rs.getURIConverter()); - if (XpectRunner.testClassloader != null) { - result.setClasspathURIContext(XpectRunner.testClassloader); + if (XpectTestGlobalState.INSTANCE.testClass() != null) { + result.setClasspathURIContext(XpectTestGlobalState.INSTANCE.testClass().getClassLoader()); result.setClasspathUriResolver(new ClassloaderClasspathUriResolver()); } else if (rs instanceof XtextResourceSet) { XtextResourceSet xrs = (XtextResourceSet) rs; diff --git a/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectUtil.java b/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectUtil.java index deed9c45..2486165a 100644 --- a/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectUtil.java +++ b/org.eclipse.xpect.ui/src/org/eclipse/xpect/ui/util/XpectUtil.java @@ -28,6 +28,7 @@ import org.eclipse.xpect.XpectFile; import org.eclipse.xpect.XpectJavaModel; import org.eclipse.xpect.runner.XpectRunner; +import org.eclipse.xpect.runner.XpectTestGlobalState; import org.eclipse.xpect.ui.internal.XpectActivator; import com.google.inject.Injector; @@ -40,8 +41,8 @@ public static XpectFile loadFile(IFile file) { Injector injector = XpectActivator.getInstance().getInjector(XpectActivator.ORG_ECLIPSE_XPECT_XPECT); XtextResourceSet rs = new XtextResourceSet(); IJavaProject javaProject = JavaCore.create(file.getProject()); - if (XpectRunner.testClassloader != null) { - rs.setClasspathURIContext(XpectRunner.testClassloader); + if (XpectTestGlobalState.INSTANCE.testClass() != null) { + rs.setClasspathURIContext(XpectTestGlobalState.INSTANCE.testClass().getClassLoader()); rs.setClasspathUriResolver(new ClassloaderClasspathUriResolver()); } else if (javaProject != null && javaProject.exists()) { rs.setClasspathURIContext(javaProject); diff --git a/org.eclipse.xpect/META-INF/MANIFEST.MF b/org.eclipse.xpect/META-INF/MANIFEST.MF index d374b142..5be66a2b 100644 --- a/org.eclipse.xpect/META-INF/MANIFEST.MF +++ b/org.eclipse.xpect/META-INF/MANIFEST.MF @@ -19,11 +19,13 @@ Require-Bundle: org.eclipse.xtext, org.antlr.runtime;bundle-version="[3.2.0,3.2.1)", org.apache.log4j;bundle-version="1.2.24", org.junit;bundle-version="4.11.0";visibility:=reexport, + junit-jupiter-api;bundle-version="5.9.1";visibility:=reexport, org.eclipse.xtext.common.types;visibility:=reexport, org.apache.log4j;bundle-version="1.2.0";visibility:=reexport, org.objectweb.asm;bundle-version="[9.5.0,10.0.0)";resolution:=optional Bundle-RequiredExecutionEnvironment: JavaSE-11 Export-Package: org.eclipse.xpect, + org.eclipse.xpect.dynamic, org.eclipse.xpect.expectation, org.eclipse.xpect.expectation.impl; x-friends:="org.eclipse.xpect.xtext.lib, diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/IXpectDynamicTestFactory.java b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/IXpectDynamicTestFactory.java new file mode 100644 index 00000000..2ec91e15 --- /dev/null +++ b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/IXpectDynamicTestFactory.java @@ -0,0 +1,17 @@ +package org.eclipse.xpect.dynamic; + +import java.util.stream.Stream; + +import org.eclipse.xpect.XpectImport; +import org.eclipse.xpect.runner.TestTitleProvider; +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.TestFactory; + +@XpectImport(TestTitleProvider.class) +public interface IXpectDynamicTestFactory { + + @TestFactory + default Stream generateTests() { + return XpectDynamicTestFactory.xpectTests(getClass()); + } +} diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTest.java b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTest.java new file mode 100644 index 00000000..f7cc2d84 --- /dev/null +++ b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTest.java @@ -0,0 +1,64 @@ +package org.eclipse.xpect.dynamic; + +import java.lang.reflect.InvocationTargetException; + +import org.eclipse.xpect.XjmTestMethod; +import org.eclipse.xpect.runner.ValidatingSetup; +import org.eclipse.xpect.runner.XpectTestGlobalState; +import org.eclipse.xpect.setup.ThisTestObject; +import org.eclipse.xpect.state.Creates; +import org.eclipse.xpect.state.StateContainer; +import org.junit.jupiter.api.DynamicTest; + +import com.google.common.base.Preconditions; + +public class XpectDynamicTest { + + private final String className; + private XjmTestMethod method; + private final StateContainer state; + + public XpectDynamicTest(StateContainer state, XjmTestMethod method) { + Preconditions.checkNotNull(method); + this.className = XpectTestGlobalState.INSTANCE.testClass().getName(); + this.method = method; + this.state = state; + } + + @Creates + public XpectDynamicTest create() { + return this; + } + + public XjmTestMethod getMethod() { + return method; + } + + public StateContainer getState() { + return state; + } + + public DynamicTest test() { + String testName = getName(); + return DynamicTest.dynamicTest(testName, () -> runInternal()); + } + + public String getName() { + String testName = formatDisplayName(method.getName(), className); + return testName; + } + + protected void runInternal() throws Throwable { + Object test = state.get(Object.class, ThisTestObject.class).get(); + try { + method.getJavaMethod().invoke(test); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + private static String formatDisplayName(String name, String className) { + return String.format("%s(%s)", name, className); + } + +} diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTestCase.java b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTestCase.java new file mode 100644 index 00000000..f79bb150 --- /dev/null +++ b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTestCase.java @@ -0,0 +1,181 @@ +package org.eclipse.xpect.dynamic; + +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.xpect.XjmMethod; +import org.eclipse.xpect.XjmTestMethod; +import org.eclipse.xpect.XpectFile; +import org.eclipse.xpect.XpectInvocation; +import org.eclipse.xpect.XpectJavaModel; +import org.eclipse.xpect.runner.IXpectURIProvider; +import org.eclipse.xpect.runner.TestExecutor; +import org.eclipse.xpect.runner.ValidatingSetup; +import org.eclipse.xpect.setup.ThisRootTestClass; +import org.eclipse.xpect.state.Creates; +import org.eclipse.xpect.state.StateContainer; +import org.junit.jupiter.api.DynamicContainer; +import org.junit.jupiter.api.DynamicTest; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + +public class XpectDynamicTestCase { + private List children; + private final StateContainer state; + private final XpectFile xpectFile; + + public XpectDynamicTestCase(StateContainer state, XpectFile file) { + this.xpectFile = file; + this.state = state; + } + + @Creates + public XpectDynamicTestCase create() { + return this; + } + + /** + *

+ * To run code before an Xpect test begins, extend this class and annotate the extending class with: + * {@code @org.eclipse.xpect.XpectReplace(org.eclipse.xpect.dynamic.XpectDynamicTestCase)} + *

+ *

+ * The extended class must then be included in the values of the annotation of the test case: + * {@code @org.eclipse.xpect.XpectImport({...class})} + *

+ */ + public void setUp() throws Throwable { + // nothing to set up + } + + /** + *

+ * To run code after an Xpect test finishes, extend this class and annotate the extending class with: + * {@code @org.eclipse.xpect.XpectReplace(org.eclipse.xpect.dynamic.XpectDynamicTestCase)} + *

+ *

+ * The extended class must then be included in the values of the annotation of the test case: + * {@code @org.eclipse.xpect.XpectImport({...class})} + *

+ */ + public void tearDown() throws Throwable { + // nothing to tear down + } + + public DynamicContainer dynamicContainer() { + return DynamicContainer.dynamicContainer(getName(), getChildren()); + } + + protected List createChildren() { + List children = Lists.newArrayList(); + if (xpectFile != null) { + /* + * With JUnit 4 runners, we can do setup validation before children run and state clean-up after children are done. + * With JUnit 5 we cannot. + * So the first test does setup validation and the last test does clean-up. + * Meaning their execution times will likely increase notably, when compared to the JUnit 4 reported time. + * 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. + * XXX: does JUnit 5 offer anything for dynamic tests, to improve this situation? + * As of writing this code, @BeforeEach and @AfterEach are only called before and after the factory method runs. + * We have a factory method with every URL we must run an Xpect test for, as those URLs are known only via an annotation. + */ + AtomicBoolean setupValidated = new AtomicBoolean(false); + AtomicBoolean setupValidationFailed = new AtomicBoolean(false); + AtomicInteger finishedChildren = new AtomicInteger(0); + XpectJavaModel xjm = xpectFile.getJavaModel(); + XjmTestMethod[] methods = xjm.getMethods().values().stream().filter(m -> m instanceof XjmTestMethod).toArray(XjmTestMethod[]::new); + XpectInvocation[] invocations = Iterables.toArray(xpectFile.getInvocations(), XpectInvocation.class); + int childrenCount = methods.length + invocations.length; + for (XjmTestMethod method : methods) { + DynamicTest test = createDynamicTest(method); + children.add(wrapTest(test, childrenCount, setupValidated, setupValidationFailed, finishedChildren)); + } + for (XpectInvocation inv : invocations) { + DynamicTest test = createDynamicTest(inv); + children.add(wrapTest(test, childrenCount, setupValidated, setupValidationFailed, finishedChildren)); + } + } + return children; + } + + protected DynamicTest createDynamicTest(XjmTestMethod method) { + StateContainer childState = TestExecutor.createState(state, TestExecutor.createTestConfiguration(method)); + return childState.get(XpectDynamicTest.class).get().test(); + } + + protected DynamicTest createDynamicTest(XpectInvocation invocation) { + StateContainer childState = TestExecutor.createState(state, TestExecutor.createXpectConfiguration(invocation)); + DynamicTest test = childState.get(XpectInvocationDynamicTest.class).get().test(); + return test; + } + + protected DynamicTest wrapTest(DynamicTest test, final int childrenCount, AtomicBoolean validatedSetup, AtomicBoolean setupValidationFailed, AtomicInteger finishedChildren) { + return DynamicTest.dynamicTest(test.getDisplayName(), () -> { + try { + if (!validatedSetup.getAndSet(true)) { + // first test is running, validate setup + try { + state.get(ValidatingSetup.class).get().validate(); + } catch (Throwable t) { + setupValidationFailed.set(true); + throw t; + } + } + if (setupValidationFailed.get()) { + throw new AssertionError("Setup validation failed"); + } + try { + setUp(); + test.getExecutable().execute(); + } finally { + tearDown(); + } + } finally { + int finished = finishedChildren.incrementAndGet(); + if (finished >= childrenCount) { + // last test is done, do clean-up + state.invalidate(); + } + } + }); + } + + protected List getChildren() { + if (children == null) + children = createChildren(); + return children; + } + + public Class getJavaTestClass() { + return state.get(Class.class, ThisRootTestClass.class).get(); + } + + public IXpectURIProvider getURIProvider() { + return state.get(IXpectURIProvider.class).get(); + } + + public StateContainer getState() { + return state; + } + + public URI getUri() { + return xpectFile.eResource().getURI(); + } + + public XpectFile getXpectFile() { + return xpectFile; + } + + public String getName() { + IXpectURIProvider uriProvider = getURIProvider(); + URI uri = getUri(); + URI deresolved = uriProvider.deresolveToProject(uri); + String pathInProject = deresolved.trimSegments(1).toString(); + String fileName = deresolved.lastSegment(); + return fileName + ": " + pathInProject; + } +} diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTestFactory.java b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTestFactory.java new file mode 100644 index 00000000..7acd4ef6 --- /dev/null +++ b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectDynamicTestFactory.java @@ -0,0 +1,182 @@ +package org.eclipse.xpect.dynamic; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.plugin.EcorePlugin; +import org.eclipse.xpect.XpectFile; +import org.eclipse.xpect.XpectJavaModel; +import org.eclipse.xpect.XpectStandaloneSetup; +import org.eclipse.xpect.registry.ITestSuiteInfo; +import org.eclipse.xpect.runner.IXpectURIProvider; +import org.eclipse.xpect.runner.TestExecutor; +import org.eclipse.xpect.runner.XpectTestFiles; +import org.eclipse.xpect.runner.XpectTestGlobalState; +import org.eclipse.xpect.runner.XpectURIProvider; +import org.eclipse.xpect.runner.XpectTestFiles.Builder; +import org.eclipse.xpect.runner.XpectTestFiles.FileRoot; +import org.eclipse.xpect.state.Configuration; +import org.eclipse.xpect.state.ResolvedConfiguration; +import org.eclipse.xpect.state.StateContainer; +import org.eclipse.xpect.util.AnnotationUtil; +import org.eclipse.xpect.util.IssueVisualizer; +import org.eclipse.xpect.util.XpectJavaModelManager; +import org.eclipse.xtext.resource.IResourceServiceProvider; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.XtextResourceFactory; +import org.eclipse.xtext.util.CancelIndicator; +import org.eclipse.xtext.util.Strings; +import org.eclipse.xtext.validation.CheckMode; +import org.eclipse.xtext.validation.IResourceValidator; +import org.eclipse.xtext.validation.Issue; +import org.junit.ComparisonFailure; +import org.junit.jupiter.api.DynamicContainer; + +import com.google.inject.Injector; + +// a lot of copy-pasting from XpectRunner, however XpectRunner has protected methods that can use different model, injector, etc. via overriding getters, so its hard to extract code without causing damage +class XpectDynamicTestFactory { + + public static Stream xpectTests(Class testClass) { + XpectDynamicTestFactory factory = new XpectDynamicTestFactory(testClass); + return factory.getChildren(); + } + + private Stream children; + private Collection files; + private final Class testClass; + private final StateContainer state; + private final IXpectURIProvider uriProvider; + private final Injector xpectInjector; + private final XpectJavaModel xpectJavaModel; + + public XpectDynamicTestFactory(Class testClass) { + this.testClass = testClass; + this.uriProvider = findUriProvider(testClass); + this.xpectInjector = findXpectInjector(); + this.xpectJavaModel = XpectJavaModelManager.createJavaModel(testClass); + /* + * NOTE: + * Do this before the state creation, otherwise the parts that depend on + * the singleton won't initialize properly and tests will fail to run! + */ + XpectTestGlobalState.INSTANCE.set(xpectJavaModel, testClass); + this.state = TestExecutor.createState(createRootConfiguration()); + } + + protected DynamicContainer createChild(URI uri) { + try { + XtextResource resource = loadXpectResource(uri); + XpectFile file = loadXpectFile(resource); + Configuration cfg = createChildConfiguration(file); + StateContainer childState = new StateContainer(state, new ResolvedConfiguration(state.getConfiguration(), cfg)); + return childState.get(XpectDynamicTestCase.class).get().dynamicContainer(); + } catch (IOException e) { + throw new AssertionError("Failed to create Xpect tests for URI: " + uri, e); + } + } + + protected Configuration createChildConfiguration(XpectFile file) { + return TestExecutor.createFileConfiguration(file); + } + + protected Stream createChildren(Class clazz) { + Collection fileUris = getFiles(); + // lazy stream + Stream tests = fileUris.stream().map(uri -> createChild(uri)); + return tests; + } + + protected Configuration createRootConfiguration() { + Configuration config = TestExecutor.createRootConfiguration(this.xpectJavaModel); + config.addDefaultValue(this); + config.addDefaultValue(IXpectURIProvider.class, this.uriProvider); + config.addFactory(XpectDynamicTestCase.class); + config.addFactory(XpectInvocationDynamicTest.class); + config.addFactory(XpectDynamicTest.class); + return config; + } + + protected IXpectURIProvider findUriProvider(Class clazz) { + String baseDir = System.getProperty("xpectBaseDir"); + String files = System.getProperty("xpectFiles"); + if (!Strings.isEmpty(baseDir) || !Strings.isEmpty(files)) { + Builder builder = new XpectTestFiles.Builder().relativeTo(FileRoot.PROJECT); + if (!Strings.isEmpty(baseDir)) + builder.withBaseDir(baseDir); + if (files != null) + for (String file : files.split(";")) { + String trimmed = file.trim(); + if (!"".equals(trimmed)) + builder.addFile(trimmed); + } + return builder.create(clazz); + + } + IXpectURIProvider provider = AnnotationUtil.newInstanceViaMetaAnnotation(clazz, XpectURIProvider.class, IXpectURIProvider.class); + if (provider != null) + return provider; + return new XpectTestFiles.Builder().relativeTo(FileRoot.CLASS).create(clazz); + } + + protected Injector findXpectInjector() { + IResourceServiceProvider rssp = IResourceServiceProvider.Registry.INSTANCE.getResourceServiceProvider(URI.createURI("foo.xpect")); + if (rssp != null) + return rssp.get(Injector.class); + if (!EcorePlugin.IS_ECLIPSE_RUNNING) + return new XpectStandaloneSetup().createInjectorAndDoEMFRegistration(); + throw new IllegalStateException("The language *.xpect is not activated"); + } + + // lazy stream, since otherwise preparation for each test runs as the tests are generated, not when each test eventually runs + public Stream getChildren() { + if (children == null) + children = createChildren(testClass); + return children; + } + + protected Collection getFiles() { + if (files == null) + files = uriProvider.getAllURIs(); + return files; + } + + protected XpectFile loadXpectFile(XtextResource res) throws IOException { + XpectFile file = !res.getContents().isEmpty() ? (XpectFile) res.getContents().get(0) : null; + if (file == null) + throw new IllegalStateException("Resource for " + res.getURI() + " is empty."); + validate(file); + validate(res); + return file; + } + + protected XtextResource loadXpectResource(URI uri) throws IOException { + XtextResourceFactory xtextResourceFactory = xpectInjector.getInstance(XtextResourceFactory.class); + XtextResource resource = (XtextResource) xtextResourceFactory.createResource(uri); + xpectJavaModel.eResource().getResourceSet().getResources().add(resource); + resource.load(null); + return resource; + } + + protected void validate(XpectFile file) { + XpectJavaModel model = file.getJavaModel(); + if (model == null || model.eIsProxy()) { + String fileName = file.eResource().getURI().lastSegment(); + String registry = ITestSuiteInfo.Registry.INSTANCE.toString(); + throw new IllegalStateException("Could not find test suite for " + fileName + ". Registry:\n" + registry); + } + } + + protected void validate(XtextResource res) { + IResourceValidator validator = res.getResourceServiceProvider().get(IResourceValidator.class); + List issues = validator.validate(res, CheckMode.ALL, CancelIndicator.NullImpl); + if (!issues.isEmpty()) { + String document = res.getParseResult().getRootNode().getText(); + String errors = new IssueVisualizer().visualize(document, issues); + throw new ComparisonFailure("Errors in " + res.getURI(), document.trim(), errors.trim()); + } + } +} diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectInvocationDynamicTest.java b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectInvocationDynamicTest.java new file mode 100644 index 00000000..ed99f50d --- /dev/null +++ b/org.eclipse.xpect/src/org/eclipse/xpect/dynamic/XpectInvocationDynamicTest.java @@ -0,0 +1,96 @@ +package org.eclipse.xpect.dynamic; + +import java.util.List; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.xpect.XjmXpectMethod; +import org.eclipse.xpect.XpectInvocation; +import org.eclipse.xpect.runner.DescriptionFactory; +import org.eclipse.xpect.runner.IXpectURIProvider; +import org.eclipse.xpect.runner.TestExecutor; +import org.eclipse.xpect.runner.XpectTestGlobalState; +import org.eclipse.xpect.state.Creates; +import org.eclipse.xpect.state.StateContainer; +import org.junit.AssumptionViolatedException; +import org.junit.jupiter.api.DynamicTest; + +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; + +public class XpectInvocationDynamicTest { + + private final Class testClass; + private final XpectInvocation invocation; + private final StateContainer state; + + public XpectInvocationDynamicTest(StateContainer state, XpectInvocation invocation) { + Preconditions.checkNotNull(invocation); + this.testClass = XpectTestGlobalState.INSTANCE.testClass(); + this.invocation = invocation; + this.state = state; + } + + @Creates + public XpectInvocationDynamicTest create() { + return this; + } + + public DynamicTest test() { + String testName = getName(); + return DynamicTest.dynamicTest(testName, () -> runInternal()); + } + + public XpectInvocation getInvocation() { + return invocation; + } + + public XjmXpectMethod getMethod() { + return invocation.getMethod(); + } + + public StateContainer getState() { + return state; + } + + protected boolean isIgnore() { + return invocation.getFile().isIgnore() || invocation.isIgnore(); + } + + protected void runInternal() throws Throwable { + if (isIgnore()) { + throw new AssumptionViolatedException("Test is ignored"); + } + TestExecutor.runTest(state, invocation); + } + + public IXpectURIProvider getURIProvider() { + return state.get(IXpectURIProvider.class).get(); + } + + public String getName() { + IXpectURIProvider uriProvider = getURIProvider(); + String testClassName = testClass.getName(); + return getTestNameForInvocation(uriProvider, testClassName, invocation); + } + + private static String getTestNameForInvocation(IXpectURIProvider uriProvider, String testClassName, XpectInvocation invocation) { + URI uri = uriProvider.deresolveToProject(EcoreUtil.getURI(invocation)); + String title = DescriptionFactory.getTitle(invocation); + List ret = DescriptionFactory.extractXpectMethodNameAndResourceURI(uri.toString()); + String fragmentToXpectMethod = ret.get(0); + String pathToResource = ret.get(1); + + String text = fragmentToXpectMethod; + + if (!Strings.isNullOrEmpty(title)) + text = text + ": " + title; + // The test name has the following format + // errors~0: This is a comment 〔path/to/file.xt〕 + return formatDisplayName(text + " \u3014" + pathToResource + "\u3015", testClassName); + } + + private static String formatDisplayName(String name, String className) { + return String.format("%s(%s)", name, className); + } +} diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/model/XpectJavaModelImplCustom.java b/org.eclipse.xpect/src/org/eclipse/xpect/model/XpectJavaModelImplCustom.java index bbaf31ea..171e7431 100644 --- a/org.eclipse.xpect/src/org/eclipse/xpect/model/XpectJavaModelImplCustom.java +++ b/org.eclipse.xpect/src/org/eclipse/xpect/model/XpectJavaModelImplCustom.java @@ -39,7 +39,6 @@ import org.eclipse.xtext.common.types.JvmVisibility; import org.eclipse.xtext.util.Pair; import org.eclipse.xtext.util.Tuples; -import org.junit.Test; import org.junit.runner.RunWith; import org.eclipse.xpect.Environment; import org.eclipse.xpect.XjmClass; @@ -245,7 +244,9 @@ private void initTestClassMethods() { if (feature instanceof JvmOperation && feature.getVisibility() == JvmVisibility.PUBLIC) { if (JvmAnnotationUtil.isAnnotatedWith(feature, Xpect.class)) xpectMethods.put(Tuples.create(true, feature.getSimpleName()), Tuples.create(type, (JvmOperation) feature)); - if (JvmAnnotationUtil.isAnnotatedWith(feature, Test.class)) + if (JvmAnnotationUtil.isAnnotatedWith(feature, org.junit.Test.class)) + xpectMethods.put(Tuples.create(false, feature.getSimpleName()), Tuples.create(type, (JvmOperation) feature)); + if (JvmAnnotationUtil.isAnnotatedWith(feature, org.junit.jupiter.api.Test.class)) xpectMethods.put(Tuples.create(false, feature.getSimpleName()), Tuples.create(type, (JvmOperation) feature)); } diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/runner/XpectRunner.java b/org.eclipse.xpect/src/org/eclipse/xpect/runner/XpectRunner.java index f92b5d27..b9cd1f95 100644 --- a/org.eclipse.xpect/src/org/eclipse/xpect/runner/XpectRunner.java +++ b/org.eclipse.xpect/src/org/eclipse/xpect/runner/XpectRunner.java @@ -72,6 +72,12 @@ public XpectRunner(Class testClass) throws InitializationError { this.uriProvider = findUriProvider(testClass); this.xpectInjector = findXpectInjector(); this.xpectJavaModel = XpectJavaModelManager.createJavaModel(testClass); + /* + * NOTE: + * Do this before the state creation, otherwise the parts that depend on + * the singleton won't initialize properly and tests will fail to run! + */ + XpectTestGlobalState.INSTANCE.set(xpectJavaModel, testClass); this.state = TestExecutor.createState(createRootConfiguration()); } diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/runner/XpectTestGlobalState.java b/org.eclipse.xpect/src/org/eclipse/xpect/runner/XpectTestGlobalState.java new file mode 100644 index 00000000..a589541f --- /dev/null +++ b/org.eclipse.xpect/src/org/eclipse/xpect/runner/XpectTestGlobalState.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2024 Simeon Andreev and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Simeon Andreev - Initial contribution and API + *******************************************************************************/ +package org.eclipse.xpect.runner; + +import org.eclipse.xpect.XpectJavaModel; + +/** + * @author Simeon Andreev - Initial contribution and API + */ +public class XpectTestGlobalState { + + public static final XpectTestGlobalState INSTANCE = new XpectTestGlobalState(); + + private XpectJavaModel model; + private Class testClass; + + public void set(XpectJavaModel model, Class testClass) { + this.model = model; + this.testClass = testClass; + } + + public XpectJavaModel model() { + return model; + } + + public Class testClass() { + return testClass; + } +} diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/services/XtResourceServiceProviderProvider.java b/org.eclipse.xpect/src/org/eclipse/xpect/services/XtResourceServiceProviderProvider.java index 9f769082..d954886c 100644 --- a/org.eclipse.xpect/src/org/eclipse/xpect/services/XtResourceServiceProviderProvider.java +++ b/org.eclipse.xpect/src/org/eclipse/xpect/services/XtResourceServiceProviderProvider.java @@ -17,6 +17,7 @@ import org.eclipse.xpect.XpectConstants; import org.eclipse.xpect.registry.ILanguageInfo; import org.eclipse.xpect.runner.XpectRunner; +import org.eclipse.xpect.runner.XpectTestGlobalState; import org.eclipse.xpect.util.IXtInjectorProvider; import com.google.inject.Injector; @@ -32,8 +33,8 @@ private XtResourceServiceProviderProvider() { } public IResourceServiceProvider get(URI uri, String contentType) { - if (XpectRunner.INSTANCE != null) { - Injector injector = IXtInjectorProvider.INSTANCE.getInjector(XpectRunner.INSTANCE.getXpectJavaModel(), uri); + if (XpectTestGlobalState.INSTANCE.model() != null) { + Injector injector = IXtInjectorProvider.INSTANCE.getInjector(XpectTestGlobalState.INSTANCE.model(), uri); if (injector != null) return injector.getInstance(IResourceServiceProvider.class); } diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/util/ClasspathUtil.java b/org.eclipse.xpect/src/org/eclipse/xpect/util/ClasspathUtil.java index e032b258..ff278d5d 100644 --- a/org.eclipse.xpect/src/org/eclipse/xpect/util/ClasspathUtil.java +++ b/org.eclipse.xpect/src/org/eclipse/xpect/util/ClasspathUtil.java @@ -26,6 +26,7 @@ import org.apache.log4j.Logger; import org.eclipse.xpect.runner.XpectRunner; +import org.eclipse.xpect.runner.XpectTestGlobalState; import com.google.common.base.Joiner; import com.google.common.collect.Sets; @@ -81,8 +82,8 @@ public static Collection findResources(String... fileNames) { } } // for some reason, ucl.getURLs() doesn't catch the current project in standalone maven surefire - if (XpectRunner.INSTANCE != null) { - Class clazz = XpectRunner.INSTANCE.getTestClass().getJavaClass(); + if (XpectTestGlobalState.INSTANCE.testClass() != null) { + Class clazz = XpectTestGlobalState.INSTANCE.testClass(); String[] segments = clazz.getName().split("\\."); String fileName = Joiner.on('/').join(segments) + ".class"; URL resource = clazz.getClassLoader().getResource(fileName); diff --git a/org.eclipse.xpect/src/org/eclipse/xpect/util/EnvironmentUtil.java b/org.eclipse.xpect/src/org/eclipse/xpect/util/EnvironmentUtil.java index 66e6807f..e07583e7 100644 --- a/org.eclipse.xpect/src/org/eclipse/xpect/util/EnvironmentUtil.java +++ b/org.eclipse.xpect/src/org/eclipse/xpect/util/EnvironmentUtil.java @@ -14,7 +14,7 @@ import org.eclipse.emf.ecore.plugin.EcorePlugin; import org.eclipse.xpect.Environment; -import org.eclipse.xpect.runner.XpectRunner; +import org.eclipse.xpect.runner.XpectTestGlobalState; import com.google.common.base.Joiner; @@ -22,7 +22,7 @@ public class EnvironmentUtil { public static final Environment ENVIRONMENT = detectEnvironement(); private static Environment detectEnvironement() { - if (XpectRunner.testClassloader != null) { + if (XpectTestGlobalState.INSTANCE.testClass() != null) { if (EcorePlugin.IS_ECLIPSE_RUNNING) return Environment.PLUGIN_TEST; else