diff --git a/com.avaloq.tools.ddk.test.core/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.test.core/META-INF/MANIFEST.MF
index ea792c11de..e3fbd6fe75 100644
--- a/com.avaloq.tools.ddk.test.core/META-INF/MANIFEST.MF
+++ b/com.avaloq.tools.ddk.test.core/META-INF/MANIFEST.MF
@@ -14,10 +14,12 @@ Require-Bundle: org.eclipse.core.runtime,
com.google.guava,
org.apache.commons.lang,
org.eclipse.emf.common,
- com.avaloq.tools.ddk
+ com.avaloq.tools.ddk,
+ junit-jupiter-api
Export-Package: com.avaloq.tools.ddk.test.core,
com.avaloq.tools.ddk.test.core.data,
com.avaloq.tools.ddk.test.core.junit.runners,
+ com.avaloq.tools.ddk.test.core.jupiter,
com.avaloq.tools.ddk.test.core.mock,
com.avaloq.tools.ddk.test.core.util
Import-Package: org.apache.logging.log4j
diff --git a/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/BugTestAwareRule.java b/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/BugTestAwareRule.java
new file mode 100644
index 0000000000..cacdde4aa2
--- /dev/null
+++ b/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/BugTestAwareRule.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Avaloq Group AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Avaloq Group AG - initial API and implementation
+ *******************************************************************************/
+package com.avaloq.tools.ddk.test.core.jupiter;
+
+import java.lang.reflect.Method;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.InvocationInterceptor;
+import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
+
+import com.avaloq.tools.ddk.test.core.BugTest;
+
+
+/**
+ * This {@link InvocationInterceptor} implementation changes the behavior for unresolved bug tests.
+ *
+ * The behavior for at test that is annotated with {@link BugTest(unresolved=true)} is the following:
+ *
+ * Test evaluation OK results in FAIL ({@link AssertionError})
+ * Test evaluation FAIL results in OK
+ * Test evaluation ERROR results in ERROR
+ *
+ *
+ *
+ * Example for a bug test:
+ *
+ *
+ * public class TestClass {
+ *
+ * @Rule
+ * public BugTestAwareRule rule = BugTestAwareRule.getInstance();
+ *
+ * @org.junit.Test
+ * @com.avaloq.tools.ddk.test.core.BugTest(value = "BUG-42", unresolved = true)
+ * public void testMethod() {
+ * org.junit.Assert.fail();
+ * }
+ * }
+ *
+ *
+ *
+ * @see BugTest
+ */
+public final class BugTestAwareRule implements InvocationInterceptor {
+
+ private static final String ERROR_TEST_MUST_FAIL = "The unresolved bug test must fail:"; //$NON-NLS-1$
+ /** The singleton instance, or {@code null} if not cached. */
+ private static BugTestAwareRule instance;
+ private static final Object LOCK = new Object();
+
+ /**
+ * Creates a new instance of {@link BugTestAwareRule}.
+ */
+ private BugTestAwareRule() {
+ // prevent instantiation
+ }
+
+ /**
+ * Returns a shared singleton instance.
+ *
+ * @return a shared instance, never {@code null}
+ */
+ public static BugTestAwareRule getInstance() {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new BugTestAwareRule();
+ }
+ return instance;
+ }
+ }
+
+ @SuppressWarnings("nls")
+ @Override
+ public void interceptTestMethod(final Invocation invocation, final ReflectiveInvocationContext invocationContext, final ExtensionContext extensionContext) throws Throwable {
+ BugTest bugTestAnnotation = extensionContext.getRequiredTestMethod().getAnnotation(BugTest.class);
+ if (bugTestAnnotation == null && extensionContext.getRequiredTestClass() != null) {
+ bugTestAnnotation = extensionContext.getRequiredTestClass().getAnnotation(BugTest.class);
+ }
+ if (bugTestAnnotation != null && bugTestAnnotation.unresolved()) {
+ try {
+ invocation.proceed();
+ } catch (AssertionError exception) {
+ return;
+ }
+ String testCase = extensionContext.getRequiredTestClass().getSimpleName() + "." + extensionContext.getRequiredTestMethod().getName();
+ throw new AssertionError(ERROR_TEST_MUST_FAIL + " " + testCase);
+ } else {
+ invocation.proceed();
+ }
+ }
+
+}
diff --git a/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/IssueAwareRule.java b/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/IssueAwareRule.java
new file mode 100644
index 0000000000..2dfe5c3155
--- /dev/null
+++ b/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/IssueAwareRule.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Avaloq Group AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Avaloq Group AG - initial API and implementation
+ *******************************************************************************/
+package com.avaloq.tools.ddk.test.core.jupiter;
+
+import java.lang.reflect.Method;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.InvocationInterceptor;
+import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
+
+import com.avaloq.tools.ddk.test.core.Issue;
+
+
+/**
+ * This {@link InvocationInterceptor} implementation changes the behavior for not fixed issues.
+ *
+ * The behavior for at test that is annotated with {@link Issue(fixed = false)} is the following:
+ *
+ * Test evaluation OK results in FAIL ({@link AssertionError})
+ * Test evaluation FAIL results in OK
+ * Test evaluation ERROR results in ERROR
+ *
+ *
+ *
+ * Example for a issue test:
+ *
+ *
+ * public class TestClass {
+ *
+ * @Rule
+ * public IssueAwareRule rule = IssueAwareRule.getInstance();
+ *
+ * @org.junit.Test
+ * @com.avaloq.tools.ddk.test.core.Issue(value = "ISSUE-42", fixed = false)
+ * public void testMethod() {
+ * org.junit.Assert.fail();
+ * }
+ * }
+ *
+ *
+ *
+ * @see Issue
+ */
+public final class IssueAwareRule implements InvocationInterceptor {
+
+ private static final String ERROR_TEST_MUST_FAIL = "The issue test for a not fixed issue must fail:"; //$NON-NLS-1$
+ /** The singleton instance, or {@code null} if not cached. */
+ private static IssueAwareRule instance;
+ private static Object lock = new Object();
+
+ /**
+ * Creates a new instance of {@link IssueAwareRule}.
+ */
+ private IssueAwareRule() {
+ // prevent instantiation
+ }
+
+ /**
+ * Returns a shared singleton instance.
+ *
+ * @return a shared instance, never {@code null}
+ */
+ public static IssueAwareRule getInstance() {
+ synchronized (lock) {
+ if (instance == null) {
+ instance = new IssueAwareRule();
+ }
+ return instance;
+ }
+ }
+
+ @SuppressWarnings("nls")
+ @Override
+ public void interceptTestMethod(final Invocation invocation, final ReflectiveInvocationContext invocationContext, final ExtensionContext extensionContext) throws Throwable {
+ Issue issueAnnotation = extensionContext.getRequiredTestMethod().getAnnotation(Issue.class);
+ if (issueAnnotation == null && extensionContext.getRequiredTestClass() != null) {
+ issueAnnotation = extensionContext.getRequiredTestClass().getAnnotation(Issue.class);
+ }
+ if (issueAnnotation != null && issueAnnotation.fixed()) {
+ try {
+ invocation.proceed();
+ } catch (AssertionError exception) {
+ return;
+ }
+ String testCase = extensionContext.getRequiredTestClass().getSimpleName() + "." + extensionContext.getRequiredTestMethod().getName();
+ throw new AssertionError(ERROR_TEST_MUST_FAIL + " " + testCase);
+ } else {
+ invocation.proceed();
+ }
+ }
+}
diff --git a/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/LoggingRule.java b/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/LoggingRule.java
new file mode 100644
index 0000000000..9152b473d6
--- /dev/null
+++ b/com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/LoggingRule.java
@@ -0,0 +1,93 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Avaloq Group AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Avaloq Group AG - initial API and implementation
+ *******************************************************************************/
+package com.avaloq.tools.ddk.test.core.jupiter;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.jupiter.api.extension.AfterEachCallback;
+import org.junit.jupiter.api.extension.BeforeEachCallback;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.TestWatcher;
+
+
+/**
+ * A test watcher that logs the start and end of each test, as well as its success or failure.
+ */
+@SuppressWarnings("nls")
+public final class LoggingRule implements TestWatcher, BeforeEachCallback, AfterEachCallback {
+
+ private static final Logger LOGGER = LogManager.getLogger(LoggingRule.class);
+
+ /** The singleton instance, or {@code null} if not cached. */
+ private static LoggingRule instance;
+
+ private static final Object LOCK = new Object();
+
+ /**
+ * Creates a new instance of {@link LoggingRule}.
+ */
+ private LoggingRule() {
+ // prevent instantiation
+ }
+
+ /**
+ * Returns a shared singleton instance.
+ *
+ * @return a shared instance, never {@code null}
+ */
+ public static LoggingRule getInstance() {
+ synchronized (LOCK) {
+ if (instance == null) {
+ instance = new LoggingRule();
+ }
+ return instance;
+ }
+ }
+
+ @Override
+ public void beforeEach(final ExtensionContext context) throws Exception {
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.info("STARTING: " + getDescriptionName(context));
+ }
+ }
+
+ /**
+ * Returns the name of a test to be logged.
+ *
+ * @param description
+ * the description, must not be {@code null}
+ * @return the description name, never {@code null}
+ */
+ private String getDescriptionName(final ExtensionContext context) {
+ return context.getRequiredTestClass().getSimpleName() + '.' + context.getRequiredTestMethod().getName();
+ }
+
+ @Override
+ public void testSuccessful(final ExtensionContext context) {
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.info("SUCCEEDED: " + getDescriptionName(context));
+ }
+ }
+
+ @Override
+ public void testFailed(final ExtensionContext context, final Throwable cause) {
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.info("FAILED: " + getDescriptionName(context));
+ }
+ }
+
+ @Override
+ public void afterEach(final ExtensionContext context) throws Exception {
+ if (LOGGER.isInfoEnabled()) {
+ LOGGER.info("FINISHED: " + getDescriptionName(context));
+ }
+ }
+}
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF
index f0a8cb3887..c4247a2e9f 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF
+++ b/com.avaloq.tools.ddk.xtext.generator.test/META-INF/MANIFEST.MF
@@ -7,19 +7,18 @@ Bundle-Vendor: Avaloq Group AG
Bundle-RequiredExecutionEnvironment: JavaSE-21
Bundle-ActivationPolicy: lazy
Fragment-Host: com.avaloq.tools.ddk.xtext.generator
-Require-Bundle: com.avaloq.tools.ddk.test.core,
+Require-Bundle: com.avaloq.tools.ddk.test.core,
com.avaloq.tools.ddk.xtext.expression,
com.avaloq.tools.ddk.xtext.test.core,
org.eclipse.xtend,
org.eclipse.xtend.typesystem.emf,
org.eclipse.xtext,
- org.junit,
org.mockito.mockito-core,
com.avaloq.tools.ddk.xtext.ui,
org.eclipse.xtext.xtext.generator,
junit-jupiter-api,
junit-jupiter-engine,
- junit-vintage-engine
+ junit-platform-suite-api
Export-Package: com.avaloq.tools.ddk.xtext.generator.test.generator
Import-Package: org.eclipse.xtext.ui.resource
Automatic-Module-Name: com.avaloq.tools.ddk.xtext.generator.test
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CodeGenerationXTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CodeGenerationXTest.java
index 00fa408b08..772a8220f5 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CodeGenerationXTest.java
+++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CodeGenerationXTest.java
@@ -10,27 +10,27 @@
*******************************************************************************/
package com.avaloq.tools.ddk.xtext.generator.expression;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import org.eclipse.xtend.expression.ExecutionContextImpl;
import org.eclipse.xtend.type.impl.java.JavaBeansMetaModel;
import org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.avaloq.tools.ddk.xtext.expression.expression.Expression;
import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext;
import com.avaloq.tools.ddk.xtext.expression.generator.CompilerX;
import com.avaloq.tools.ddk.xtext.expression.generator.GenModelUtilX;
import com.avaloq.tools.ddk.xtext.generator.test.util.GeneratorTestUtil;
-import com.avaloq.tools.ddk.xtext.test.AbstractXtextTest;
+import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTest;
/**
* Tests the code generation as implemented by CodeGenerationX wrapped by {@link CompilerX}.
*/
-@SuppressWarnings("nls")
+@SuppressWarnings({"nls", "PMD.SignatureDeclareThrowsException"})
public class CodeGenerationXTest extends AbstractXtextTest {
private CompilerX getCompiler() {
@@ -61,7 +61,7 @@ protected String getTestSourceFileName() {
}
@Test
- public void testliterals() throws Exception {
+ void testliterals() throws Exception {
// CHECKSTYLE:CONSTANTS-OFF
assertEquals("42", compile("42")); // NOPMD
assertEquals("4.2", compile("4.2")); // NOPMD
@@ -73,19 +73,19 @@ public void testliterals() throws Exception {
}
@Test
- public void testListLiterals() throws Exception {
+ void testListLiterals() throws Exception {
assertEquals("java.util.Collections. emptyList()", compile("{}")); // NOPMD
assertEquals("java.util.Collections.singletonList(1)", compile("{1}")); // NOPMD
assertEquals("com.google.common.collect.Lists.newArrayList(1, 2, 3)", compile("{1,2,3}")); // NOPMD
}
@Test
- public void testIdentifiers() throws Exception {
+ void testIdentifiers() throws Exception {
assertEquals("obj.getTrue()", compile("^true")); // NOPMD
}
@Test
- public void testBracketing() throws Exception {
+ void testBracketing() throws Exception {
// CHECKSTYLE:CONSTANTS-OFF
assertEquals("(4 + 2) * 3", compile("(4 + 2) * 3")); // NOPMD
assertEquals("(4 + 2) * 3 * 4", compile("(4 + 2) * 3 * 4")); // NOPMD
@@ -97,7 +97,7 @@ public void testBracketing() throws Exception {
}
@Test
- public void testBooleanLogic() throws Exception {
+ void testBooleanLogic() throws Exception {
// CHECKSTYLE:CONSTANTS-OFF
assertEquals("true", compile("true")); // NOPMD
assertEquals("false", compile("false")); // NOPMD
@@ -116,7 +116,7 @@ public void testBooleanLogic() throws Exception {
}
@Test
- public void testArithmetics() throws Exception {
+ void testArithmetics() throws Exception {
// CHECKSTYLE:CONSTANTS-OFF
assertEquals("4 + 2", compile("4 + 2")); // NOPMD
assertEquals("4 - 2", compile("4 - 2")); // NOPMD
@@ -127,7 +127,7 @@ public void testArithmetics() throws Exception {
}
@Test
- public void testPrefixExpressions() throws Exception {
+ void testPrefixExpressions() throws Exception {
// CHECKSTYLE:CONSTANTS-OFF
assertEquals("-(4 * 2)", compile("-(4 * 2)")); // NOPMD
assertEquals("-(-42)", compile("-(-42)")); // NOPMD
@@ -141,7 +141,7 @@ public void testPrefixExpressions() throws Exception {
}
@Test
- public void testInfixExpressions() throws Exception {
+ void testInfixExpressions() throws Exception {
// CHECKSTYLE:CONSTANTS-OFF
assertEquals("(true ? 1 : 2) + 3", compile("(true ? 1 : 2) + 3")); // NOPMD
assertEquals("!(true ? true : false)", compile("!(true ? true : false)")); // NOPMD
@@ -150,28 +150,28 @@ public void testInfixExpressions() throws Exception {
}
@Test
- public void testImplicitVariable() throws Exception {
+ void testImplicitVariable() throws Exception {
assertEquals("obj", compile("this")); // NOPMD
}
@Test
- public void testCasting() throws Exception {
+ void testCasting() throws Exception {
assertEquals("((org.eclipse.emf.ecore.EObject) obj)", compile("(ecore::EObject) this")); // NOPMD
}
@Test
- public void testTypes() throws Exception {
+ void testTypes() throws Exception {
assertEquals("org.eclipse.emf.ecore.EObject", compile("ecore::EObject")); // NOPMD
assertEquals("String", compile("java::lang::String")); // NOPMD
}
@Test
- public void testIsInstance() throws Exception {
+ void testIsInstance() throws Exception {
assertEquals("obj instanceof org.eclipse.emf.ecore.EObject", compile("ecore::EObject.isInstance(this)")); // NOPMD
}
@Test
- public void testEContainerNavigation() throws Exception {
+ void testEContainerNavigation() throws Exception {
// CHECKSTYLE:CONSTANTS-OFF
assertEquals("obj.eContainer()", compile("this.eContainer")); // NOPMD
assertEquals("obj.eContainer()", compile("this.eContainer()")); // NOPMD
@@ -179,7 +179,7 @@ public void testEContainerNavigation() throws Exception {
}
@Test
- public void testTypeSelect() throws Exception {
+ void testTypeSelect() throws Exception {
assertEquals(// NOPMD
"com.google.common.collect.Iterables.filter(obj.getFoos(), org.eclipse.emf.ecore.EObject.class)", compile("this.foos.typeSelect(ecore::EObject)"));
assertEquals(// NOPMD
@@ -187,13 +187,13 @@ public void testTypeSelect() throws Exception {
}
@Test
- public void testCollectionExpression() throws Exception {
+ void testCollectionExpression() throws Exception {
assertEquals(// NOPMD
"com.google.common.collect.Iterables.filter(java.util.Collections.singletonList(obj), new com.google.common.base.Predicate() { public boolean apply(Object e) {return true;} })", compile("{this}.select(e|true)"));
}
@Test
- public void testMultipleNavigations() throws Exception {
+ void testMultipleNavigations() throws Exception {
assertEquals(// NOPMD
"/* NOT COMPILABLE: Complex expressions like \"this.eContainer.eContainer\" cannot be translated to Java. Consider rewriting the expression or using a JAVA extension. */", compile("this.eContainer.eContainer"));
assertEquals(// NOPMD
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CompilationContextTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CompilationContextTest.java
index 31a6b9d154..b0ada1b629 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CompilationContextTest.java
+++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/CompilationContextTest.java
@@ -10,8 +10,8 @@
*******************************************************************************/
package com.avaloq.tools.ddk.xtext.generator.expression;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
@@ -21,41 +21,41 @@
import org.eclipse.xtend.expression.ExecutionContextImpl;
import org.eclipse.xtend.type.impl.java.JavaBeansMetaModel;
import org.eclipse.xtend.typesystem.Type;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.avaloq.tools.ddk.xtext.expression.generator.CompilationContext;
-@SuppressWarnings("nls")
+@SuppressWarnings({"nls", "PMD.SignatureDeclareThrowsException"})
public class CompilationContextTest {
@Test
- public void isExtension() {
+ void isExtension() {
ExecutionContextImpl executionContext = new ExecutionContextImpl();
executionContext.registerMetaModel(new JavaBeansMetaModel());
ExtensionFile extensionFile = ParseFacade.file(new InputStreamReader(getClass().getResourceAsStream("/com/avaloq/tools/ddk/xtext/generator/expression/TestExtensions.ext"), StandardCharsets.UTF_8), "TestExtensions.ext");
executionContext = (ExecutionContextImpl) executionContext.cloneWithResource(extensionFile);
final CompilationContext context = new CompilationContext(executionContext, null);
- assertTrue("test extension not identified", context.isExtension("test"));
+ assertTrue(context.isExtension("test"), "test extension not identified");
}
@Test
- public void analyze() {
+ void analyze() {
ExecutionContextImpl executionContext = new ExecutionContextImpl();
executionContext.registerMetaModel(new JavaBeansMetaModel());
final CompilationContext context = new CompilationContext(executionContext, null);
Type expectedType = executionContext.getTypeForName("Integer");
- assertSame("Cannot analyze Integer", expectedType, context.analyze("1 + 3"));
+ assertSame(expectedType, context.analyze("1 + 3"), "Cannot analyze Integer");
expectedType = executionContext.getTypeForName("Real");
- assertSame("Cannot analyze Real", expectedType, context.analyze("1 + 3.33"));
+ assertSame(expectedType, context.analyze("1 + 3.33"), "Cannot analyze Real");
expectedType = executionContext.getTypeForName("String");
- assertSame("Cannot analyse String 'foo'", expectedType, context.analyze("\'foo\'"));
- assertSame("Cannot analyse String \"foo \" ", expectedType, context.analyze("\"foo\""));
- assertSame("Cannot analyse String \"foo\" + \'bar\'", expectedType, context.analyze("\"foo\" + \'bar\'"));
+ assertSame(expectedType, context.analyze("\'foo\'"), "Cannot analyse String 'foo'");
+ assertSame(expectedType, context.analyze("\"foo\""), "Cannot analyse String \"foo \" ");
+ assertSame(expectedType, context.analyze("\"foo\" + \'bar\'"), "Cannot analyse String \"foo\" + \'bar\'");
}
}
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExpressionsExtentionsTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExpressionsExtentionsTest.java
index bc4be72506..541d9f785e 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExpressionsExtentionsTest.java
+++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/expression/ExpressionsExtentionsTest.java
@@ -10,16 +10,16 @@
*******************************************************************************/
package com.avaloq.tools.ddk.xtext.generator.expression;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.avaloq.tools.ddk.xtext.expression.expression.Expression;
import com.avaloq.tools.ddk.xtext.expression.generator.ExpressionExtensions;
import com.avaloq.tools.ddk.xtext.generator.test.util.GeneratorTestUtil;
-import com.avaloq.tools.ddk.xtext.test.AbstractXtextTest;
+import com.avaloq.tools.ddk.xtext.test.jupiter.AbstractXtextTest;
@SuppressWarnings("nls")
@@ -39,8 +39,9 @@ protected String getTestSourceFileName() {
}
@Test
- public final void serialize() throws IOException {
+ @SuppressWarnings({"PMD.SignatureDeclareThrowsException"})
+ void serialize() throws IOException {
Expression e = (Expression) getXtextTestUtil().getModel("test.expression." + getXtextTestUtil().getFileExtension(), "let x = 1 : 0");
- assertEquals("Simple serialization works", "let x = 1 : 0", ExpressionExtensions.serialize(e));
+ assertEquals("let x = 1 : 0", ExpressionExtensions.serialize(e), "Simple serialization works");
}
}
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java
index 5371698eec..d4c3f4ed0a 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java
+++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/generator/GeneratorTestSuite.java
@@ -10,8 +10,8 @@
*******************************************************************************/
package com.avaloq.tools.ddk.xtext.generator.test.generator;
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
+import org.junit.platform.suite.api.SelectClasses;
+import org.junit.platform.suite.api.Suite;
import com.avaloq.tools.ddk.xtext.generator.expression.CodeGenerationXTest;
import com.avaloq.tools.ddk.xtext.generator.expression.CompilationContextTest;
@@ -21,17 +21,18 @@
/**
- * Empty class serving only as holder for JUnit4 annotations.
+ * Junit5 version of test suites. does not implement the logic in our DiscerningSuite.
*/
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
+@Suite
+@SelectClasses({
// @Format-Off
- GraphTest.class,
- EClassComparatorTest.class,
CodeGenerationXTest.class,
CompilationContextTest.class,
- ExpressionsExtentionsTest.class
-// @Format-On
+ ExpressionsExtentionsTest.class,
+ EClassComparatorTest.class,
+ GraphTest.class
+ // @Format-On
})
+
public class GeneratorTestSuite {
}
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/EClassComparatorTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/EClassComparatorTest.java
index 36e0c08de5..acd767479c 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/EClassComparatorTest.java
+++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/EClassComparatorTest.java
@@ -15,7 +15,7 @@
import static org.eclipse.emf.ecore.EcorePackage.Literals.EDATA_TYPE;
import static org.eclipse.emf.ecore.EcorePackage.Literals.EOBJECT;
import static org.eclipse.emf.ecore.EcorePackage.Literals.EPACKAGE;
-import static org.junit.Assert.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
@@ -23,7 +23,7 @@
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.xtext.XtextPackage;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.avaloq.tools.ddk.xtext.expression.generator.EClassComparator;
import com.google.common.base.Function;
@@ -38,7 +38,7 @@ public class EClassComparatorTest {
private final Function mapping = Functions. identity();
@Test
- public void testSorting() {
+ void testSorting() {
List sorted = EClassComparator.sortedGroups(Lists.newArrayList(ECLASS, EDATA_TYPE, EPACKAGE, ECLASSIFIER), mapping);
assertEquals(Lists.newArrayList(ECLASS, EDATA_TYPE, EPACKAGE, ECLASSIFIER), sorted);
@@ -53,7 +53,7 @@ public void testSorting() {
}
@Test
- public void testSortingWithEObject() {
+ void testSortingWithEObject() {
List sorted = EClassComparator.sortedGroups(Lists.newArrayList(EOBJECT, ECLASS), mapping);
assertEquals(Lists.newArrayList(ECLASS, EOBJECT), sorted);
@@ -62,7 +62,7 @@ public void testSortingWithEObject() {
}
@Test
- public void testSortingByEPackage() {
+ void testSortingByEPackage() {
ListMultimap sorted = EClassComparator.sortedEPackageGroups(Lists.newArrayList(EOBJECT, ECLASS), mapping);
assertEquals(2, sorted.size());
assertEquals(1, sorted.keySet().size());
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/GraphTest.java b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/GraphTest.java
index 670e99d62a..d8a7a3316d 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/GraphTest.java
+++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/test/util/GraphTest.java
@@ -10,13 +10,13 @@
*******************************************************************************/
package com.avaloq.tools.ddk.xtext.generator.test.util;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
import java.util.List;
import java.util.Map;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import com.avaloq.tools.ddk.xtext.expression.generator.Graph;
import com.google.common.collect.HashMultimap;
@@ -29,7 +29,7 @@ public class GraphTest {
// CHECKSTYLE:CONSTANTS-OFF
@Test
- public void testTopologicalSorting() {
+ void testTopologicalSorting() {
Multimap graph = HashMultimap.create();
graph.put("7", "11");
graph.put("7", "8");
@@ -47,7 +47,7 @@ public void testTopologicalSorting() {
}
@Test
- public void testDependencyCycle() {
+ void testDependencyCycle() {
Multimap graph = ImmutableMultimap.of("1", "2", "2", "3", "3", "1");
try {
diff --git a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.xtend b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.xtend
index abdec160f9..25be2f5007 100644
--- a/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.xtend
+++ b/com.avaloq.tools.ddk.xtext.generator.test/src/com/avaloq/tools/ddk/xtext/generator/xbase/test/XbaseGeneratorFragmentTest.xtend
@@ -12,7 +12,6 @@
package com.avaloq.tools.ddk.xtext.generator.xbase.test
import com.avaloq.tools.ddk.test.core.BugTest
-import junit.framework.TestCase
import org.eclipse.emf.common.util.BasicEList
import org.eclipse.emf.ecore.EPackage
import org.eclipse.xtext.AbstractElement
@@ -25,19 +24,27 @@ import org.eclipse.xtext.ParserRule
import org.eclipse.xtext.RuleCall
import org.eclipse.xtext.TypeRef
import org.eclipse.xtext.XtextPackage
-import org.eclipse.xtext.testing.XtextRunner
-import org.junit.Test
-import org.junit.runner.RunWith
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
import org.eclipse.xtext.xtext.generator.xbase.XbaseUsageDetector
+import org.junit.jupiter.api.^extension.ExtendWith
+import org.eclipse.xtext.testing.extensions.InjectionExtension
+import org.junit.jupiter.api.Test
+import static org.junit.jupiter.api.Assertions.assertFalse
+import static org.junit.jupiter.api.Assertions.assertThrows
+import static org.junit.jupiter.api.Assertions.assertTrue
+import org.junit.jupiter.api.^extension.RegisterExtension
+import com.avaloq.tools.ddk.test.core.jupiter.BugTestAwareRule
/**
* Tests for {@link XbaseUsageDetector}.
*/
-@RunWith(XtextRunner)
-class XbaseGeneratorFragmentTest extends TestCase {
+@ExtendWith(InjectionExtension)
+class XbaseGeneratorFragmentTest {
+
+ @RegisterExtension
+ public BugTestAwareRule bugTestRule = BugTestAwareRule.getInstance();
val thisPackageName = "thisPackage"
val xtypePackageName = "xtype"
@@ -134,9 +141,9 @@ class XbaseGeneratorFragmentTest extends TestCase {
/**
* Test usesXImportSection() when passed a null XtextResource
*/
- @Test(expected=NullPointerException)
+ @Test
def void testUsesXImportSectionWithNullGrammar() {
- detector.usesXImportSection(null)
+ assertThrows(NullPointerException, [|detector.usesXImportSection(null)]);
}
/**
@@ -161,7 +168,7 @@ class XbaseGeneratorFragmentTest extends TestCase {
val usesXImportSection = detector.usesXImportSection(mockGrammar)
// ASSERT
- assertFalse("usesXImportSection() should return false when the grammar does not use XImportSection", usesXImportSection)
+ assertFalse(usesXImportSection, "usesXImportSection() should return false when the grammar does not use XImportSection")
}
/**
@@ -187,7 +194,7 @@ class XbaseGeneratorFragmentTest extends TestCase {
val usesXImportSection = detector.usesXImportSection(mockGrammar)
// ASSERT
- assertTrue("usesXImportSection() should return true when the grammar uses XImportSection", usesXImportSection)
+ assertTrue(usesXImportSection, "usesXImportSection() should return true when the grammar uses XImportSection")
}
}
diff --git a/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF b/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF
index 8c344dfd08..260565d767 100644
--- a/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF
+++ b/com.avaloq.tools.ddk.xtext.test.core/META-INF/MANIFEST.MF
@@ -26,7 +26,8 @@ Require-Bundle: com.avaloq.tools.ddk.xtext,
org.hamcrest.library,
com.avaloq.tools.ddk.check.runtime.core,
org.eclipse.emf.common,
- com.avaloq.tools.ddk
+ com.avaloq.tools.ddk,
+ junit-jupiter-api
Import-Package: org.slf4j, org.apache.logging.log4j,org.apache.log4j
Export-Package: com.avaloq.tools.ddk.xtext.test,
com.avaloq.tools.ddk.xtext.test.contentassist,
@@ -34,6 +35,7 @@ Export-Package: com.avaloq.tools.ddk.xtext.test,
com.avaloq.tools.ddk.xtext.test.formatting,
com.avaloq.tools.ddk.xtext.test.generator,
com.avaloq.tools.ddk.xtext.test.junit.runners,
+ com.avaloq.tools.ddk.xtext.test.jupiter,
com.avaloq.tools.ddk.xtext.test.jvmmodel,
com.avaloq.tools.ddk.xtext.test.linking,
com.avaloq.tools.ddk.xtext.test.model,
diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/AbstractTestUtil.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/AbstractTestUtil.java
index a0f1bf4db9..567f161149 100644
--- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/AbstractTestUtil.java
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/AbstractTestUtil.java
@@ -31,7 +31,7 @@ public abstract class AbstractTestUtil { // NOPMD we really do want default impl
*
* @return {@link ITestProjectManager}
*/
- protected ITestProjectManager getTestProjectManager() {
+ public ITestProjectManager getTestProjectManager() {
return testProjectManager;
}
@@ -51,7 +51,7 @@ protected ITestProjectManager createTestProjectManager() {
* @param family
* to wait for.
*/
- protected void waitForJobsOfFamily(final Object family) {
+ public void waitForJobsOfFamily(final Object family) {
boolean wasInterrupted;
do {
try {
@@ -96,7 +96,7 @@ public Object getEditorJobFamily(final IEditorPart editor) {
* @param editor
* editor part
*/
- protected void waitForEditorJobs(final IEditorPart editor) {
+ public void waitForEditorJobs(final IEditorPart editor) {
Object jobFamily = getEditorJobFamily(editor);
if (jobFamily != null) {
waitForJobsOfFamily(jobFamily);
diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/IllegalJUnitAnnotation.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/IllegalJUnitAnnotation.java
index 5fb849ad79..b39008e408 100644
--- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/IllegalJUnitAnnotation.java
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/IllegalJUnitAnnotation.java
@@ -13,10 +13,10 @@
/**
* Raised when a test wants to annotate a method with either a {@link @BeforeClass} or a {@link @AfterClass} annotation.
*/
-class IllegalJUnitAnnotation extends RuntimeException {
+public class IllegalJUnitAnnotation extends RuntimeException {
private static final long serialVersionUID = 1L;
- IllegalJUnitAnnotation() {
+ public IllegalJUnitAnnotation() {
super("Invalid annotation found. BeforeClass and AfterClass annotations are not permitted when using the AbstractXtextTest framework. Use the methods 'beforeAllTests' and 'afterAllTests' instead."); //$NON-NLS-1$
}
diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractTest.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractTest.java
new file mode 100644
index 0000000000..34eba33761
--- /dev/null
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractTest.java
@@ -0,0 +1,440 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Avaloq Group AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Avaloq Group AG - initial API and implementation
+ *******************************************************************************/
+package com.avaloq.tools.ddk.xtext.test.jupiter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.common.util.WrappedException;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+import org.eclipse.xtext.testing.extensions.InjectionExtension;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import com.avaloq.tools.ddk.test.core.jupiter.BugTestAwareRule;
+import com.avaloq.tools.ddk.test.core.jupiter.IssueAwareRule;
+import com.avaloq.tools.ddk.test.core.jupiter.LoggingRule;
+import com.avaloq.tools.ddk.test.core.mock.ExtensionRegistryMock;
+import com.avaloq.tools.ddk.test.core.mock.ServiceMock;
+import com.avaloq.tools.ddk.xtext.test.AbstractTestUtil;
+import com.avaloq.tools.ddk.xtext.test.ITestProjectManager;
+import com.avaloq.tools.ddk.xtext.test.TestInformation;
+import com.avaloq.tools.ddk.xtext.test.TestSource;
+import com.avaloq.tools.ddk.xtext.test.XtextTestSource;
+import com.google.common.collect.ImmutableList;
+
+
+/**
+ * Provides a test class specific custom test framework for tests that run in the ACF environment.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ */
+@ExtendWith(InjectionExtension.class)
+@SuppressWarnings("nls")
+@TestInstance(Lifecycle.PER_CLASS)
+public abstract class AbstractTest {
+
+ /**
+ * Prefix for customer sources.
+ * Consider also: com.avaloq.tools.asmd.testbase.TestUtil.CUSTR_PREFIX
+ * The duplicated definition of the prefix must be harmonized based on harmonization of test plugins.
+ */
+ protected static final String CUSTOMER_SOURCE_PREFIX = "custr_";
+
+ protected static final String PROJECT_NAME = "SDK";
+
+ private static final String HIGH_LATENCY_PROPERTY = "com.avaloq.tools.hl.supported";
+
+ private static final String LANGUAGE_VERSION_CACHING_PROPERTY = "com.avaloq.tools.LanguageConfigCaching";
+
+ private static Map, TestInformation> testInformationMap = new HashMap, TestInformation>();
+
+ @RegisterExtension
+ // CHECKSTYLE:CHECK-OFF Visibility MethodRules cannot be private
+ public final LoggingRule watchman = LoggingRule.getInstance();
+ // CHECKSTYLE:CHECK-ON Visibility
+ /**
+ * Enables support for unresolved bug tests.
+ */
+ @RegisterExtension
+ // CHECKSTYLE:CHECK-OFF Visibility MethodRules cannot be private
+ public BugTestAwareRule bugTestRule = BugTestAwareRule.getInstance();
+ // CHECKSTYLE:CHECK-ON Visibility
+ @RegisterExtension
+ // CHECKSTYLE:CHECK-OFF Visibility MethodRules cannot be private
+ public final IssueAwareRule issueRule = IssueAwareRule.getInstance();
+ // CHECKSTYLE:CHECK-ON Visibility
+
+ protected ITestProjectManager getTestProjectManager() {
+ return getTestUtil().getTestProjectManager();
+ }
+
+ /**
+ * Returns the URI for the given target source file.
+ *
+ * @param fullSourceName
+ * full source name
+ * @return URI of source
+ */
+ public URI getTargetSourceUri(final String fullSourceName) {
+ return getTestProjectManager().createTestSourceUri(fullSourceName);
+ }
+
+ /**
+ * Returns a list of all kernel source file names that this test will require.
+ * This can be overridden to extend or replace the returned list.
+ *
+ * @return list of all required kernel source file names
+ */
+ protected List getRequiredSourceFileNames() {
+ List requiredSources = new LinkedList();
+ String testSourceFileName = getTestSourceFileName();
+ if (testSourceFileName != null && testSourceFileName.length() > 0) {
+ requiredSources.add(testSourceFileName);
+ }
+ return requiredSources;
+ }
+
+ /**
+ * Registers all required sources for this test.
+ * This method can be overridden to register other required source files.
+ */
+ protected void registerRequiredSources() {
+ addSourcesToWorkspace(getRequiredSourceFileNames());
+ }
+
+ /**
+ * Non-static instance set up before all tests.
+ */
+ @BeforeAll
+ public final void setUp() {
+ synchronized (testInformationMap) {
+ if (!testInformationMap.containsKey(this.getClass())) {
+ testInformationMap.put(this.getClass(), new TestInformation());
+ beforeAllTests();
+ }
+ }
+ }
+
+ /**
+ * Non-static instance tear down after all tests.
+ */
+ @AfterAll
+ public final void tearDown() {
+ synchronized (testInformationMap) {
+ afterAllTests();
+ ExtensionRegistryMock.assertUnMocked();
+ ServiceMock.assertAllMocksRemoved();
+ testInformationMap.remove(this.getClass());
+ }
+ }
+
+ /**
+ * This method prepares the test environment for a test. It is called by the JUnit framework before each test.
+ * If it is run the first time, it calls the beforeClass method first. Do not call this method manually!
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ */
+ @BeforeEach
+ public final void before() {
+ beforeEachTest();
+ }
+
+ /**
+ * This method cleans up the test environment after a test. It is called by the JUnit framework after each test.
+ * If no more tests are to be run, it calls the afterClass method. Do not call this method manually!
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ */
+ @AfterEach
+ public final void after() {
+ afterEachTest();
+ }
+
+ /**
+ * Prepares the test class after the test class has been instantiated. This method can be used to setup the test class before any test is run.
+ * Do not use JUnit annotation.
+ * Exceptions are wrapped and handed over to the JUnit framework.
+ */
+ protected void beforeAllTests() {
+ // check method annotations to ensure test framework policies
+ System.setProperty(HIGH_LATENCY_PROPERTY, Boolean.FALSE.toString());
+ System.setProperty(LANGUAGE_VERSION_CACHING_PROPERTY, Boolean.FALSE.toString());
+ getTestProjectManager().setup(ImmutableList. of());
+ registerRequiredSources();
+ getTestProjectManager().build();
+ }
+
+ /**
+ * After the last task has run but before the test class instance has been garbage collected, this method is called to clean up the test environment.
+ * Do not use JUnit annotation.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ */
+ protected void afterAllTests() {
+ getTestProjectManager().teardown();
+ System.clearProperty(LANGUAGE_VERSION_CACHING_PROPERTY);
+ System.setProperty(HIGH_LATENCY_PROPERTY, Boolean.TRUE.toString());
+ }
+
+ /**
+ * Prepares for the next test. This method can be used to (re-)initialize the test environment before a (next) test is run. Resource allocations must be dealt
+ * with in {@link afterEachTest}.
+ * Do not use JUnit annotation.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ */
+ @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract")
+ protected void beforeEachTest() {
+ // empty
+ }
+
+ /**
+ * Called after each test to clean up initializations done in {@link beforeEachTest}.
+ * Do not use JUnit annotation.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ */
+ @SuppressWarnings("PMD.EmptyMethodInAbstractClassShouldBeAbstract")
+ protected void afterEachTest() {
+ // empty
+ }
+
+ /**
+ * Registers a source that is required by the test class.
+ * The source will be removed from the system when {@link afterAllTests} is called.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ *
+ * @param sourceFileName
+ * the name of the file where the source is located, and where the content of the source shall be written to. This is the source name available
+ * during the test.
+ */
+ protected void addSourceToWorkspace(final String sourceFileName) {
+ addSourceToWorkspace(sourceFileName, getResourceContent(sourceFileName));
+ }
+
+ /**
+ * Registers a kernel source that is required by the test class.
+ * The source will be removed from the system when {@link afterAllTests} is called.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ *
+ * @param sourceFileName
+ * the name of the file where the content of the source shall be written to. This is the source name available
+ * during the test.
+ * @param sourceContent
+ * the content of the source that shall be written to the file in workspace.
+ */
+ protected void addKernelSourceToWorkspace(final String sourceFileName, final CharSequence sourceContent) {
+ addSourceToWorkspace(sourceFileName, sourceContent.toString());
+ }
+
+ /**
+ * Registers a customer source that is required by the test class.
+ * The source will be removed from the system when {@link afterAllTests} is called.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ *
+ * @param sourceFileName
+ * the name of the file where the content of the source shall be written to. This is the source name available
+ * during the test.
+ * @param sourceContent
+ * the content of the source that shall be written to the file in workspace.
+ */
+ protected void addCustomerSourceToWorkspace(final String sourceFileName, final CharSequence sourceContent) {
+ addSourceToWorkspace(CUSTOMER_SOURCE_PREFIX.concat(sourceFileName), sourceContent.toString());
+ }
+
+ /**
+ * Registers a source that is required by the test class.
+ * The source will be removed from the system when {@link afterAllTests} is called.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ *
+ * @param sourceFileName
+ * the name of the file where the content of the source shall be written to. This is the source name available
+ * during the test.
+ * @param sourceContent
+ * the content of the source that shall be written to the file in workspace.
+ */
+ private void addSourceToWorkspace(final String sourceFileName, final String sourceContent) {
+ createTestSource(sourceFileName, sourceContent);
+ }
+
+ /**
+ * Returns the string contents of the loaded resource with the given name.
+ *
+ * @param sourceFileName
+ * the file name
+ * @return the string contents of the loaded resource
+ */
+ protected String getResourceContent(final String sourceFileName) {
+ return TestSource.getResourceContent(this.getClass(), sourceFileName);
+ }
+
+ /**
+ * Registers a set of sources that is required by the test class.
+ * The sources will be removed from the system when {@link afterAllTests} is called.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ *
+ * @param sourceFileNames
+ * the names of the files where the sources are located, and where the content of the sources shall be written to.
+ */
+ private void addSourcesToWorkspace(final List sourceFileNames) {
+ try {
+ new WorkspaceModifyOperation() {
+ @Override
+ protected void execute(final IProgressMonitor monitor) throws CoreException, InvocationTargetException, InterruptedException {
+ for (String sourceFileName : sourceFileNames) {
+ addSourceToWorkspace(sourceFileName);
+ }
+ }
+ }.run(new NullProgressMonitor());
+ } catch (InvocationTargetException e) {
+ throw new WrappedException("failed adding sources to workspace", e);
+ } catch (InterruptedException e) {
+ throw new WrappedException("adding sources to workspace interrupted", e);
+ }
+ }
+
+ protected Collection extends TestSource> getTestSources() {
+ return getTestProjectManager().getTestSources();
+ }
+
+ /**
+ * Returns the kernel {@link TestSource} for the given sourceFileName.
+ *
+ * @param sourceFileName
+ * the file name of the {@link TestSource}
+ * @return the {@link TestSource} for the given sourceFileName
+ */
+ protected XtextTestSource getTestSource(final String sourceFileName) {
+ return (XtextTestSource) getTestProjectManager().getTestSource(sourceFileName);
+ }
+
+ /**
+ * Returns the kernel {@link TestSource} for this test class.
+ *
+ * @return the {@link TestSource} for this test class
+ */
+ protected TestSource getTestSource() {
+ return getTestProjectManager().getTestSource(getTestSourceFileName());
+ }
+
+ /**
+ * Get the name of the main test source file.
+ *
+ * @return the file name of the main test source file
+ */
+ protected abstract String getTestSourceFileName();
+
+ /**
+ * The default implementation returns the name of the test class for the model name of the test source.
+ * A test class needs to override this, if the name of the main test source model differs from the default.
+ *
+ * @return the name of the main test source model
+ */
+ protected String getTestSourceModelName() {
+ return this.getClass().getSimpleName();
+ }
+
+ /**
+ * Wait for validation jobs to finish.
+ */
+ protected void waitForValidation() {
+ waitForJobsOfFamily(org.eclipse.xtext.ui.editor.validation.ValidationJob.XTEXT_VALIDATION_FAMILY);
+ }
+
+ /**
+ * Wait for jobs of a given family to finish.
+ *
+ * @param family
+ * to wait for.
+ */
+ protected void waitForJobsOfFamily(final Object family) {
+ getTestUtil().waitForJobsOfFamily(family);
+ }
+
+ /**
+ * Wait for synchronization jobs on opening/closing the editor.
+ *
+ * @param editor
+ * editor part
+ */
+ protected void waitForEditorJobs(final IEditorPart editor) {
+ getTestUtil().waitForEditorJobs(editor);
+ }
+
+ /**
+ * Wait for jobs of a given family to appear. A {@code null} family will
+ * cause this to wait for any job.
+ *
+ * @param family
+ * to wait for, may be {@code null}
+ * @param timeout
+ * ms to wait for.
+ */
+ protected void waitForJobOfFamilyToAppear(final Object family, final long timeout) {
+ final long timeLimit = System.currentTimeMillis() + timeout;
+ do {
+ if (Job.getJobManager().find(family).length > 0) {
+ return;
+ }
+ } while (System.currentTimeMillis() < timeLimit);
+ }
+
+ /**
+ * Returns the test information for the current test class.
+ *
+ * @return information for the current test class
+ */
+ protected TestInformation getTestInformation() {
+ synchronized (testInformationMap) {
+ return testInformationMap.get(this.getClass());
+ }
+ }
+
+ /**
+ * Create a test source for testing from an existing file.
+ *
+ * @param sourceFileName
+ * file name for source
+ * @param content
+ * content of source
+ * @return a new {@link TestSource} with the given parameters
+ */
+
+ protected TestSource createTestSource(final String sourceFileName, final String content) {
+ TestSource testSource = new TestSource(sourceFileName, content);
+ getTestProjectManager().addSourceToProject(testSource);
+ return testSource;
+ }
+
+ /**
+ * Get the test class utility for this test. The minimum functionality is given by
+ * AbstractTestUtil, which does not require that any methods be overridden. Tests
+ * that require more than this minimal functionality must override this method.
+ *
+ * This method is expected to always return the same instance, even when invoked on different instances of the test class. This is because the associated
+ * {@link ITestProjectManager} is stateful and required by {@link #beforeAllTests()}, {@link #afterAllTests()}, and {@link #getTestSources()}.
+ *
+ * @return the test class utility for this test.
+ */
+ protected abstract AbstractTestUtil getTestUtil();
+
+}
diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextTest.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextTest.java
new file mode 100644
index 0000000000..01a43815b9
--- /dev/null
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextTest.java
@@ -0,0 +1,114 @@
+/*******************************************************************************
+ * Copyright (c) 2025 Avaloq Group AG and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Avaloq Group AG - initial API and implementation
+ *******************************************************************************/
+package com.avaloq.tools.ddk.xtext.test.jupiter;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.resource.XtextResource;
+
+import com.avaloq.tools.ddk.xtext.test.AbstractXtextTestUtil;
+import com.avaloq.tools.ddk.xtext.test.XtextTestSource;
+
+
+/**
+ * Provides a test class specific custom test framework for xtext languages. Provides a language specific {@link AbstractXtextTestUtil}.
+ * All exceptions are wrapped and handed over to the JUnit framework.
+ */
+public abstract class AbstractXtextTest extends AbstractTest {
+
+ /**
+ * Returns a language specific {@link AbstractXtextTestUtil}. Test classes specify which TestUtil to use by implementing this method.
+ *
+ * @return a language specific {@link AbstractXtextTestUtil}
+ */
+ protected abstract AbstractXtextTestUtil getXtextTestUtil();
+
+ @Override
+ protected final AbstractXtextTestUtil getTestUtil() {
+ return getXtextTestUtil();
+ }
+
+ @Override
+ protected XtextTestSource getTestSource() {
+ return (XtextTestSource) super.getTestSource();
+ }
+
+ @Override
+ protected XtextTestSource createTestSource(final String sourceFileName, final String content) {
+ XtextTestSource testSource = new XtextTestSource(sourceFileName, content, getTestUtil().getResourceSet());
+ getTestProjectManager().addSourceToProject(testSource);
+ return testSource;
+ }
+
+ /**
+ * The default implementation returns the name of the source model calling {@link getTestSourceModelName} and adds the default file extension for the grammar
+ * of this test. A test class needs to override this, if the name of the main test source file differs from the default.
+ *
+ * @return the name of the main test source file
+ */
+ @Override
+ protected String getTestSourceFileName() {
+ return this.getTestSourceModelName() + '.' + getXtextTestUtil().getFileExtension();
+ }
+
+ /**
+ * Returns the xtext resource loaded by {@link loadPrimarySource}.
+ *
+ * @return
+ * the xtext resource loaded by {@link loadPrimarySource}.
+ */
+ protected XtextResource getXtextTestResource() {
+ return getTestSource(getTestSourceFileName()).getXtextResource();
+ }
+
+ /**
+ * Returns the semantic model from the xtext resource loaded by {@link loadPrimarySource}.
+ *
+ * @return
+ * the semantic model from the xtext resource loaded by {@link loadPrimarySource}.
+ */
+ protected EObject getSemanticModel() {
+ return getXtextTestResource().getParseResult().getRootASTElement();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addKernelSourceToWorkspace(final String sourceFileName, final CharSequence sourceContent) {
+ refreshSourceContent(sourceFileName, sourceContent.toString());
+ super.addKernelSourceToWorkspace(sourceFileName, sourceContent);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void addCustomerSourceToWorkspace(final String sourceFileName, final CharSequence sourceContent) {
+ refreshSourceContent(CUSTOMER_SOURCE_PREFIX + sourceFileName, sourceContent.toString());
+ super.addCustomerSourceToWorkspace(sourceFileName, sourceContent);
+ }
+
+ /**
+ * Refresh the source content if it had already been added previously to the workspace.
+ *
+ * @param sourceFileName
+ * the source file name
+ * @param content
+ * the content
+ */
+ private void refreshSourceContent(final String sourceFileName, final String content) {
+ XtextTestSource xtextTestSource = getTestSource(sourceFileName);
+ if (xtextTestSource != null) {
+ xtextTestSource.setContent(content);
+ }
+ }
+
+}