From c0dd0ad4fb1817d3e0048cdfee9d5f8f69393a8d Mon Sep 17 00:00:00 2001 From: U004458 Date: Thu, 6 Nov 2025 16:09:02 +0800 Subject: [PATCH] feat: Migrate com.avaloq.tools.ddk.xtext.generator.test to Junit5 - completely remove junit4 dependencies, hence removing the need for dependency in junit-vintage-engine - create AbstractTest and AbstractXtextTest that does not need junit4 runners - create junit5 extensions for LoggingRule, BugTestAwareRule, and IssueAwareRule to move away from the junit4 @Rule annotation and use @RegisterExtension instead --- .../META-INF/MANIFEST.MF | 4 +- .../test/core/jupiter/BugTestAwareRule.java | 100 ++++ .../ddk/test/core/jupiter/IssueAwareRule.java | 99 ++++ .../ddk/test/core/jupiter/LoggingRule.java | 93 ++++ .../META-INF/MANIFEST.MF | 5 +- .../expression/CodeGenerationXTest.java | 40 +- .../expression/CompilationContextTest.java | 24 +- .../expression/ExpressionsExtentionsTest.java | 11 +- .../test/generator/GeneratorTestSuite.java | 19 +- .../test/util/EClassComparatorTest.java | 10 +- .../xtext/generator/test/util/GraphTest.java | 10 +- .../test/XbaseGeneratorFragmentTest.xtend | 27 +- .../META-INF/MANIFEST.MF | 4 +- .../ddk/xtext/test/AbstractTestUtil.java | 6 +- .../xtext/test/IllegalJUnitAnnotation.java | 4 +- .../ddk/xtext/test/jupiter/AbstractTest.java | 440 ++++++++++++++++++ .../xtext/test/jupiter/AbstractXtextTest.java | 114 +++++ 17 files changed, 934 insertions(+), 76 deletions(-) create mode 100644 com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/BugTestAwareRule.java create mode 100644 com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/IssueAwareRule.java create mode 100644 com.avaloq.tools.ddk.test.core/src/com/avaloq/tools/ddk/test/core/jupiter/LoggingRule.java create mode 100644 com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractTest.java create mode 100644 com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextTest.java 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: + *

+ *

+ *

+ * 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 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); + } + } + +}