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();
-
-}
+/*******************************************************************************
+ * 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/AbstractUtilTest.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractUtilTest.java
index 3ee301be93..a435e3c40d 100644
--- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractUtilTest.java
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractUtilTest.java
@@ -1,75 +1,75 @@
-/*******************************************************************************
- * 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 static java.util.Collections.singleton;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import org.eclipse.core.resources.IFile;
-import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IStorage;
-import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.xtext.resource.IResourceDescription;
-import org.eclipse.xtext.resource.IResourceDescription.Delta;
-import org.eclipse.xtext.ui.resource.IStorage2UriMapper;
-import org.eclipse.xtext.ui.resource.Storage2UriMapperImpl;
-import org.eclipse.xtext.util.Pair;
-import org.eclipse.xtext.util.Tuples;
-
-
-/**
- * A base class for util test classes, which prepares common required mocks.
- */
-@SuppressWarnings("nls")
-public abstract class AbstractUtilTest extends AbstractXtextTest {
-
- public static final String TEST_PROJECT_NAME = "TestProjectName";
- private static final String DUMMY_PATH = TEST_PROJECT_NAME + "/TEST/";
-
- // CHECKSTYLE:CHECK-OFF VisibilityModifierCheck
- protected static Delta delta;
- protected static IResourceDescription oldDesc;
- protected static IResourceDescription newDesc;
- protected static URI uriCorrect;
- protected static Resource resource;
- protected static IStorage2UriMapper mapperCorrect;
- protected static IFile file;
-
- // CHECKSTYLE:CHECK-ON VisibilityModifierCheck
-
- /**
- * Prepare mocks for all tests.
- */
- public static void prepareMocksBase() {
- oldDesc = mock(IResourceDescription.class);
- newDesc = mock(IResourceDescription.class);
- delta = mock(Delta.class);
- resource = mock(Resource.class);
- uriCorrect = mock(URI.class);
- when(uriCorrect.isPlatformResource()).thenReturn(true);
- when(uriCorrect.isFile()).thenReturn(true);
- when(uriCorrect.toFileString()).thenReturn(DUMMY_PATH);
- when(uriCorrect.toPlatformString(true)).thenReturn(DUMMY_PATH);
- when(delta.getNew()).thenReturn(newDesc);
- when(delta.getOld()).thenReturn(oldDesc);
- when(delta.getUri()).thenReturn(uriCorrect);
- when(resource.getURI()).thenReturn(uriCorrect);
- file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(uriCorrect.toPlatformString(true)));
- Iterable> storages = singleton(Tuples. create(file, file.getProject()));
- mapperCorrect = mock(Storage2UriMapperImpl.class);
- when(mapperCorrect.getStorages(uriCorrect)).thenReturn(storages);
- }
-
-}
+/*******************************************************************************
+ * 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 static java.util.Collections.singleton;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.xtext.resource.IResourceDescription;
+import org.eclipse.xtext.resource.IResourceDescription.Delta;
+import org.eclipse.xtext.ui.resource.IStorage2UriMapper;
+import org.eclipse.xtext.ui.resource.Storage2UriMapperImpl;
+import org.eclipse.xtext.util.Pair;
+import org.eclipse.xtext.util.Tuples;
+
+
+/**
+ * A base class for util test classes, which prepares common required mocks.
+ */
+@SuppressWarnings("nls")
+public abstract class AbstractUtilTest extends AbstractXtextTest {
+
+ public static final String TEST_PROJECT_NAME = "TestProjectName";
+ private static final String DUMMY_PATH = TEST_PROJECT_NAME + "/TEST/";
+
+ // CHECKSTYLE:CHECK-OFF VisibilityModifierCheck
+ protected static Delta delta;
+ protected static IResourceDescription oldDesc;
+ protected static IResourceDescription newDesc;
+ protected static URI uriCorrect;
+ protected static Resource resource;
+ protected static IStorage2UriMapper mapperCorrect;
+ protected static IFile file;
+
+ // CHECKSTYLE:CHECK-ON VisibilityModifierCheck
+
+ /**
+ * Prepare mocks for all tests.
+ */
+ public static void prepareMocksBase() {
+ oldDesc = mock(IResourceDescription.class);
+ newDesc = mock(IResourceDescription.class);
+ delta = mock(Delta.class);
+ resource = mock(Resource.class);
+ uriCorrect = mock(URI.class);
+ when(uriCorrect.isPlatformResource()).thenReturn(true);
+ when(uriCorrect.isFile()).thenReturn(true);
+ when(uriCorrect.toFileString()).thenReturn(DUMMY_PATH);
+ when(uriCorrect.toPlatformString(true)).thenReturn(DUMMY_PATH);
+ when(delta.getNew()).thenReturn(newDesc);
+ when(delta.getOld()).thenReturn(oldDesc);
+ when(delta.getUri()).thenReturn(uriCorrect);
+ when(resource.getURI()).thenReturn(uriCorrect);
+ file = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(uriCorrect.toPlatformString(true)));
+ Iterable> storages = singleton(Tuples. create(file, file.getProject()));
+ mapperCorrect = mock(Storage2UriMapperImpl.class);
+ when(mapperCorrect.getStorages(uriCorrect)).thenReturn(storages);
+ }
+
+}
diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractValidationTest.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractValidationTest.java
index c248425e23..b8e426ebc9 100644
--- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractValidationTest.java
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractValidationTest.java
@@ -1,1218 +1,1218 @@
-/*******************************************************************************
- * 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 static org.eclipse.xtext.validation.ValidationMessageAcceptor.INSIGNIFICANT_INDEX;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.eclipse.emf.common.util.BasicDiagnostic;
-import org.eclipse.emf.common.util.Diagnostic;
-import org.eclipse.emf.common.util.EList;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.eclipse.osgi.util.NLS;
-import org.eclipse.xtext.EcoreUtil2;
-import org.eclipse.xtext.diagnostics.AbstractDiagnostic;
-import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic;
-import org.eclipse.xtext.nodemodel.ICompositeNode;
-import org.eclipse.xtext.nodemodel.INode;
-import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
-import org.eclipse.xtext.resource.XtextResource;
-import org.eclipse.xtext.resource.XtextSyntaxDiagnostic;
-import org.eclipse.xtext.util.CancelIndicator;
-import org.eclipse.xtext.validation.AbstractValidationDiagnostic;
-import org.eclipse.xtext.validation.FeatureBasedDiagnostic;
-import org.eclipse.xtext.validation.RangeBasedDiagnostic;
-import org.eclipse.xtext.xbase.lib.Pair;
-
-import com.avaloq.tools.ddk.xtext.test.XtextTestSource;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.inject.Provider;
-
-
-/**
- * Base class for validation tests.
- */
-@SuppressWarnings({"nls", "PMD.ExcessiveClassLength"})
-// CHECKSTYLE:CHECK-OFF MultipleStringLiterals
-// CHECKSTYLE:OFF MagicNumber
-public abstract class AbstractValidationTest extends AbstractXtextMarkerBasedTest {
-
- public static final int NO_ERRORS = 0;
-
- static final String NO_ERRORS_FOUND_ON_RESOURCE_MESSAGE = "Expecting no errors on resource";
-
- private static final int SEVERITY_UNDEFINED = -1;
- private static final Map CODE_TO_NAME = ImmutableMap.of(Diagnostic.INFO, "INFO", Diagnostic.WARNING, "WARNING", Diagnostic.ERROR, "ERROR");
-
- private static final String LINE_BREAK = "\n";
- private static final String DOT_AND_LINEBREAK = "'." + LINE_BREAK;
-
- /**
- * All diagnostics of the current testing file.
- */
- private Diagnostic fileDiagnostics;
-
- /**
- * During validation of a source we monitor diagnostics, that are found in the source but were not expected by the test.
- * If the validation test is strict, then we will display these unexpected diagnostics as test error.
- */
- private final Set unexpectedDiagnostics = Sets.newLinkedHashSet();
- private final Set unexpectedResourceDiagnostics = Sets.newLinkedHashSet();
-
- /**
- * validation results calculated during test setUp.
- *
- * @return the diagnostic for the primary test source file
- */
- private Diagnostic getPrimaryDiagnostics() {
- Object obj = getTestInformation().getTestObject(Diagnostic.class);
- assertNotNull(obj, "getPrimaryDiagnostics(): Diagnostics of primary source not null.");
- return (Diagnostic) obj;
- }
-
- /**
- * Returns the unexpectedDiagnostics.
- *
- * @return the unexpectedDiagnostics
- */
- protected Set getUnexpectedDiagnostics() {
- return unexpectedDiagnostics;
- }
-
- /**
- * Returns the unexpectedDiagnostics.
- *
- * @return the unexpectedDiagnostics
- */
- protected Set getUnexpectedResourceDiagnostics() {
- return unexpectedResourceDiagnostics;
- }
-
- /**
- * Assertion testing for {@link AbstractValidationDiagnostic validation issues} at a given source position.
- */
- protected class XtextDiagnosticAssertion extends AbstractModelAssertion {
-
- /** Issue code of the diagnostic. */
- private final String issueCode;
- /** Issue message of the diagnostic. */
- private final String message;
- /**
- * Indicates whether the assertion must find the issue.
- * Assertion creates an error if the existence of issue code for the target eobject doesn't correspond to the value of issueMustBeFound.
- */
- private final boolean issueMustBeFound;
-
- private final int expectedSeverity;
-
- protected XtextDiagnosticAssertion(final String issueCode, final boolean issueMustBeFound) {
- this(issueCode, issueMustBeFound, SEVERITY_UNDEFINED, null);
- }
-
- protected XtextDiagnosticAssertion(final String issueCode, final boolean issueMustBeFound, final int severity, final String message) {
- this.issueCode = issueCode;
- this.issueMustBeFound = issueMustBeFound;
- this.expectedSeverity = severity;
- this.message = message;
- }
-
- /**
- * Check if the given issue code is found among issue codes for the object, located at the given position.
- *
- * @param root
- * root object of the document
- * @param pos
- * position to locate the target object
- */
- @Override
- public void apply(final EObject root, final Integer pos) {
- final Diagnostic diagnostics = validate(root);
- final BasicDiagnostic diagnosticsOnTargetPosition = new BasicDiagnostic();
- boolean issueFound = false;
- int actualSeverity = SEVERITY_UNDEFINED;
- boolean expectedSeverityMatches = false;
- boolean expectedMessageMatches = false;
- String actualMessage = "";
-
- for (AbstractValidationDiagnostic avd : Iterables.filter(diagnostics.getChildren(), AbstractValidationDiagnostic.class)) {
- if (diagnosticPositionEquals(pos, avd)) {
- // Add issue to the list of issues at the given position
- diagnosticsOnTargetPosition.add(avd);
- if (avd.getIssueCode().equals(issueCode)) {
- issueFound = true;
- actualSeverity = avd.getSeverity();
- // True if the expected severity is not set, or if matches with the actual one
- expectedSeverityMatches = expectedSeverity == SEVERITY_UNDEFINED || expectedSeverity == actualSeverity;
- actualMessage = avd.getMessage();
- // True if message matches with actual message or message is null
- expectedMessageMatches = message == null || actualMessage.equals(message);
- if (issueMustBeFound) {
- // Remove the diagnostic from the list of non-expected diagnostics
- getUnexpectedDiagnostics().remove(avd);
- // Don't need to display error messages
- if (expectedSeverityMatches && expectedMessageMatches) {
- return;
- }
- }
- }
- }
- }
-
- // Create error message
- createErrorMessage(pos, diagnosticsOnTargetPosition, issueFound, expectedSeverityMatches, actualSeverity, expectedMessageMatches, actualMessage);
- }
-
- /**
- * Create an error message (if needed) based on the given input parameters.
- *
- * @param pos
- * position in the source to associate the message with
- * @param diagnosticsOnTargetPosition
- * diagnostics on the specifies position
- * @param issueFound
- * specifies whether an issue has been found at the given position
- * @param expectedSeverityMatches
- * true if expected severity equals actual one, false otherwise
- * @param actualSeverity
- * actual severity
- * @param expectedMessageMatches
- * expected message matches
- * @param actualMessage
- * actual message
- */
- private void createErrorMessage(final Integer pos, final BasicDiagnostic diagnosticsOnTargetPosition, final boolean issueFound, final boolean expectedSeverityMatches, final int actualSeverity, final boolean expectedMessageMatches, final String actualMessage) {
- StringBuilder errorMessage = new StringBuilder(180);
- if (issueMustBeFound && !issueFound) {
- errorMessage.append("Expected issue not found. Code '").append(issueCode).append('\n');
- } else if (!issueMustBeFound && issueFound) {
- errorMessage.append("There should be no issue with the code '").append(issueCode).append(DOT_AND_LINEBREAK);
- }
- if (issueFound && !expectedMessageMatches) {
- errorMessage.append("Expected message does not match. Expected: '").append(message).append("', Actual: '").append(actualMessage).append('\n');
- }
- // If the expected issue has been found, but the actual severity does not match with expected one
- if (issueMustBeFound && issueFound && !expectedSeverityMatches) {
- errorMessage.append("Severity does not match. Expected: ").append(CODE_TO_NAME.get(expectedSeverity)).append(". Actual: ").append(CODE_TO_NAME.get(actualSeverity)).append(".\n");
- }
- // Memorize error message
- if (errorMessage.length() > 0) {
- if (!diagnosticsOnTargetPosition.getChildren().isEmpty()) {
- errorMessage.append(" All issues at this position:\n");
- errorMessage.append(diagnosticsToString(diagnosticsOnTargetPosition, false));
- }
- memorizeErrorOnPosition(pos, errorMessage.toString());
- }
- }
-
- /**
- * Compare if the position of the given diagnostic equals to the given position in text.
- *
- * @param pos
- * position in text
- * @param avd
- * diagnostic that we check, if it has the same position as the given position in text
- * @return
- * TRUE if diagnostic has the same position as the given one, FALSE otherwise.
- */
- protected boolean diagnosticPositionEquals(final Integer pos, final AbstractValidationDiagnostic avd) {
- if (avd instanceof FeatureBasedDiagnostic && ((FeatureBasedDiagnostic) avd).getFeature() != null) {
- List nodes = NodeModelUtils.findNodesForFeature(avd.getSourceEObject(), ((FeatureBasedDiagnostic) avd).getFeature());
- if (nodes.isEmpty()) {
- INode node = NodeModelUtils.getNode(avd.getSourceEObject());
- INode firstNonHiddenLeafNode = getXtextTestUtil().findFirstNonHiddenLeafNode(node);
- if (firstNonHiddenLeafNode == null) {
- return issueMustBeFound;
- } else if (firstNonHiddenLeafNode.getTotalOffset() == pos) {
- return true;
- }
- } else {
- int avdIndex = ((FeatureBasedDiagnostic) avd).getIndex();
- for (int i = 0; i < nodes.size(); i++) {
- if (avdIndex == INSIGNIFICANT_INDEX || avdIndex == i) {
- INode firstNonHiddenLeafNode = getXtextTestUtil().findFirstNonHiddenLeafNode(nodes.get(i));
- if (firstNonHiddenLeafNode == null) {
- return issueMustBeFound;
- } else if (firstNonHiddenLeafNode.getTotalOffset() == pos) {
- return true;
- }
- }
- }
- }
- } else if (avd instanceof RangeBasedDiagnostic) {
- if (((RangeBasedDiagnostic) avd).getOffset() == pos) {
- return true;
- }
- } else {
- INode node = NodeModelUtils.getNode(avd.getSourceEObject());
- INode firstNonHiddenLeafNode = getXtextTestUtil().findFirstNonHiddenLeafNode(node);
- if (firstNonHiddenLeafNode == null) {
- return issueMustBeFound;
- } else if (firstNonHiddenLeafNode.getTotalOffset() == pos) {
- return true;
- }
- }
- return false;
- }
- }
-
- /**
- * Assertion testing for {@link AbstractValidationDiagnostic validation issues} at a given source position.
- */
- private class ResourceDiagnosticAssertion extends AbstractModelAssertion {
-
- /** Issue code of the diagnostic. */
- private final String issueCode;
- /** Issue message of the diagnostic. */
- private final String message;
- /**
- * Indicates whether the assertion must find the issue.
- * Assertion creates an error if the existence of issue code for the target eobject doesn't correspond to the value of issueMustBeFound.
- */
- private final boolean issueMustBeFound;
-
- private final int expectedSeverity;
-
- protected ResourceDiagnosticAssertion(final String issueCode, final boolean issueMustBeFound, final int severity, final String message) {
- this.issueCode = issueCode;
- this.issueMustBeFound = issueMustBeFound;
- this.expectedSeverity = severity;
- this.message = message;
- }
-
- /**
- * Check if the given issue code is found among issue codes for the object, located at the given position.
- *
- * @param root
- * root object of the document
- * @param pos
- * position to locate the target object
- */
- @Override
- public void apply(final EObject root, final Integer pos) {
- Iterable diagnostics = null;
- switch (expectedSeverity) {
- case Diagnostic.ERROR:
- diagnostics = root.eResource().getErrors();
- break;
- case Diagnostic.WARNING:
- diagnostics = root.eResource().getWarnings();
- break;
- case SEVERITY_UNDEFINED:
- diagnostics = Iterables.concat(root.eResource().getErrors(), root.eResource().getWarnings());
- break;
- }
- final List diagnosticsOnTargetPosition = Lists.newArrayList();
- boolean issueFound = false;
- int actualSeverity = expectedSeverity;
- boolean expectedMessageMatches = false;
- String actualMessage = "";
-
- for (AbstractDiagnostic diag : Iterables.filter(diagnostics, AbstractDiagnostic.class)) {
- if (diagnosticPositionEquals(pos, diag)) {
- // Add issue to the list of issues at the given position
- diagnosticsOnTargetPosition.add(diag);
- if (diag.getCode() != null && diag.getCode().equals(issueCode)) {
- issueFound = true;
- if (expectedSeverity == SEVERITY_UNDEFINED) {
- actualSeverity = root.eResource().getErrors().contains(diag) ? Diagnostic.ERROR : Diagnostic.WARNING;
- }
- actualMessage = diag.getMessage();
- // True if message matches with actual message or message is null
- expectedMessageMatches = message == null || actualMessage.equals(message);
- // Don't need to display error messages
- if (issueMustBeFound) {
- // Remove the diagnostic from the list of non-expected diagnostics
- getUnexpectedResourceDiagnostics().remove(diag);
- // Don't need to display error messages
- if (expectedMessageMatches) {
- return;
- }
- }
- }
- }
- }
-
- // Create error message
- createErrorMessage(pos, diagnosticsOnTargetPosition, issueFound, true, actualSeverity, expectedMessageMatches, actualMessage);
- }
-
- /**
- * Create an error message (if needed) based on the given input parameters.
- *
- * @param pos
- * position in the source to associate the message with
- * @param diagnosticsOnTargetPosition
- * diagnostics on the specifies position
- * @param issueFound
- * specifies whether an issue has been found at the given position
- * @param expectedSeverityMatches
- * true if expected severity equals actual one, false otherwise
- * @param actualSeverity
- * actual severity
- * @param expectedMessageMatches
- * expected message matches
- * @param actualMessage
- * actual message
- */
- private void createErrorMessage(final Integer pos, final List diagnosticsOnTargetPosition, final boolean issueFound, final boolean expectedSeverityMatches, final int actualSeverity, final boolean expectedMessageMatches, final String actualMessage) {
- StringBuilder errorMessage = new StringBuilder(175);
- if (issueMustBeFound && !issueFound) {
- errorMessage.append("Expected issue not found. Code '").append(issueCode).append('\n');
- } else if (!issueMustBeFound && issueFound) {
- errorMessage.append("There should be no issue with the code '").append(issueCode).append(DOT_AND_LINEBREAK);
- }
- if (issueFound && !expectedMessageMatches) {
- errorMessage.append("Expected message does not match. Expected: '").append(message).append("', Actual: '").append(actualMessage).append('\n');
- }
- // If the expected issue has been found, but the actual severity does not match with expected one
- if (issueMustBeFound && issueFound && !expectedSeverityMatches) {
- errorMessage.append("Severity does not match. Expected: ").append(CODE_TO_NAME.get(expectedSeverity)).append(". Actual: ").append(CODE_TO_NAME.get(actualSeverity)).append(".\n");
- }
- // Memorize error message
- if (errorMessage.length() > 0) {
- if (!diagnosticsOnTargetPosition.isEmpty()) {
- errorMessage.append(" All issues at this position:\n");
- errorMessage.append(diagnosticsToString(diagnosticsOnTargetPosition, false));
- }
- memorizeErrorOnPosition(pos, errorMessage.toString());
- }
- }
-
- /**
- * Compare if the position of the given diagnostic equals to the given position in text.
- *
- * @param pos
- * position in text
- * @param diagnostic
- * diagnostic that we check, if it has the same position as the given position in text
- * @return
- * {@code true} if diagnostic has the same position as the given one, {@code false} otherwise.
- */
- private boolean diagnosticPositionEquals(final Integer pos, final AbstractDiagnostic diagnostic) {
- return diagnostic.getOffset() == pos;
- }
- }
-
- /**
- * Get a cached version of an object associated with the root object for a given key.
- *
- * @param
- * type of the associated object
- * @param root
- * root EObject
- * @param key
- * key identifying the type of the associated object
- * @param provider
- * provider to deliver an object if there is no cached version
- * @return
- * cached version of the associated object
- */
- protected T getCached(final EObject root, final String key, final Provider provider) {
- XtextResource res = (XtextResource) root.eResource();
- return res.getCache().get(key, res, provider);
- }
-
- /**
- * Validate the model.
- *
- * @param root
- * root EObject to validate
- * @return
- * validation results
- */
- protected Diagnostic validate(final EObject root) {
- return getCached(root, "DIAGNOSTIC", () -> getXtextTestUtil().getDiagnostician().validate(root));
- }
-
- /**
- * Display the path from root object to the target EObject.
- *
- * @param eObject
- * object to display the object path for
- * @param offset
- * string offset that is added in the beginning of each line
- * @return
- * object hierarchy as string (each object on a single line)
- */
- private String pathFromRootAsString(final EObject eObject, final String offset) {
- List hierarchy = Lists.newLinkedList();
-
- EObject currentObject = eObject;
- while (currentObject != null) {
- hierarchy.add(0, offset + currentObject.toString());
- currentObject = currentObject.eContainer();
- }
-
- return String.join("\n", hierarchy);
- }
-
- /**
- * Persist list diagnostics into string to display the list of issue codes.
- *
- * @param diagnostics
- * list of diagnostics
- * @param displayPathToTargetObject
- * if true, the path through the object hierarchy is printed out up to the root node
- * @return
- * string with list of issue codes, separated with a line break
- */
- // TODO (ACF-4153) generalize for all kinds of errors and move to AbstractXtextTest
- private String diagnosticsToString(final Diagnostic diagnostics, final boolean displayPathToTargetObject) {
- StringBuilder sb = new StringBuilder();
- for (Diagnostic diagnostic : diagnostics.getChildren()) {
- if (diagnostic instanceof AbstractValidationDiagnostic) {
- AbstractValidationDiagnostic avd = (AbstractValidationDiagnostic) diagnostic;
- sb.append(" ");
- sb.append(avd.getIssueCode());
- if (displayPathToTargetObject) {
- sb.append(" at line: ");
- ICompositeNode compositeNode = NodeModelUtils.findActualNodeFor(avd.getSourceEObject());
- if (compositeNode != null) {
- sb.append(compositeNode.getStartLine());
- } else {
- sb.append("Unknown");
- }
- sb.append(" on \n");
- sb.append(pathFromRootAsString(avd.getSourceEObject(), " "));
- }
- sb.append(LINE_BREAK);
- }
- }
- return sb.toString();
- }
-
- /**
- * Persist list diagnostics into string to display the list of issue codes.
- *
- * @param diagnostics
- * list of diagnostics
- * @param displayPathToTargetObject
- * if true, the path through the object hierarchy is printed out up to the root node
- * @return
- * string with list of issue codes, separated with a line break
- */
- // TODO (ACF-4153) generalize for all kinds of errors and move to AbstractXtextTest
- private String diagnosticsToString(final List diagnostics, final boolean displayPathToTargetObject) {
- StringBuilder sb = new StringBuilder(25);
- for (Resource.Diagnostic diagnostic : diagnostics) {
- if (diagnostic instanceof AbstractDiagnostic) {
- AbstractDiagnostic diag = (AbstractDiagnostic) diagnostic;
- sb.append(" ");
- sb.append(diag.getCode());
- if (displayPathToTargetObject) {
- sb.append(" at line: ");
- sb.append(diag.getLine());
- sb.append(" on \n");
- sb.append(" ");
- sb.append(diag.getUriToProblem());
- }
- sb.append(LINE_BREAK);
- }
- }
- return sb.toString();
- }
-
- @Override
- protected void beforeAllTests() {
- super.beforeAllTests();
- if (getTestSource() != null) {
- Diagnostic primaryDiagnostics = getXtextTestUtil().getDiagnostician().validate(getSemanticModel());
- getTestInformation().putTestObject(Diagnostic.class, primaryDiagnostics);
- }
- }
-
- /**
- * Register a new validation marker with the given issue code. Expects an info.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String info(final String issueCode) {
- return info(issueCode, null);
- }
-
- /**
- * Register a new validation marker with the given issue code and message. Expects an info.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @param message
- * the expected issue message
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String info(final String issueCode, final String message) {
- return addAssertion(new XtextDiagnosticAssertion(issueCode, true, Diagnostic.INFO, message));
- }
-
- /**
- * Register a new validation marker with the given issue code. Expects a warning if the condition is {@code true}, no diagnostic otherwise.
- *
- * @param condition
- * the condition when the marker is expected
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String warningIf(final boolean condition, final String issueCode) {
- if (condition) {
- return warning(issueCode);
- } else {
- return noDiagnostic(issueCode);
- }
- }
-
- /**
- * Register a new validation marker with the given issue code. Expects a warning.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String warning(final String issueCode) {
- return warning(issueCode, null);
- }
-
- /**
- * Register a new validation marker with the given issue code and message. Expects a warning.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @param message
- * the expected issue message
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String warning(final String issueCode, final String message) {
- return addAssertion(new XtextDiagnosticAssertion(issueCode, true, Diagnostic.WARNING, message));
- }
-
- /**
- * Register a new validation marker with the given issue code. Expects an error if the condition is {@code true}, no diagnostic otherwise.
- *
- * @param condition
- * the condition when the marker is expected
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String errorIf(final boolean condition, final String issueCode) {
- if (condition) {
- return error(issueCode);
- } else {
- return noDiagnostic(issueCode);
- }
- }
-
- /**
- * Register a new validation marker with the given issue code. Expects an error.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String error(final String issueCode) {
- return error(issueCode, null);
- }
-
- /**
- * Register a new validation marker with the given issue code and message. Expects an error.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @param message
- * the expected issue message
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String error(final String issueCode, final String message) {
- return addAssertion(new XtextDiagnosticAssertion(issueCode, true, Diagnostic.ERROR, message));
- }
-
- /**
- * Register a new validation marker with the given issue code.
- * The issue is expected to be found in the test file.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String diagnostic(final String issueCode) {
- return diagnostic(issueCode, null);
- }
-
- /**
- * Register a new validation marker with the given issue code and message.
- * The issue and message are expected to be found in the test file.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @param message
- * the expected issue message
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String diagnostic(final String issueCode, final String message) {
- return addAssertion(new XtextDiagnosticAssertion(issueCode, true, SEVERITY_UNDEFINED, message));
- }
-
- /**
- * Register a new linking error validation marker.
- * The issue is expected to be found in the test file.
- *
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String linkingError() {
- return linkingError(null);
- }
-
- /**
- * Register a new linking error validation marker with the given message.
- * The issue is expected to be found in the test file.
- *
- * @param message
- * issuethe expected issue message
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String linkingError(final String message) {
- return addAssertion(new ResourceDiagnosticAssertion(org.eclipse.xtext.diagnostics.Diagnostic.LINKING_DIAGNOSTIC, true, Diagnostic.ERROR, message));
- }
-
- /**
- * Register a new resource validation marker with the given issue code and message.
- * The issue is expected to be found in the test file.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @param message
- * the expected issue message
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String resourceDiagnostic(final String issueCode, final String message) {
- return addAssertion(new ResourceDiagnosticAssertion(issueCode, true, Diagnostic.ERROR, message));
- }
-
- /**
- * Register a new validation marker with the given issue code.
- * The issue is expected NOT to be found in the test file.
- *
- * @param issueCode
- * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
- * @return
- * unique marker that can be used in the input string to mark a position that should be validated
- */
- protected String noDiagnostic(final String issueCode) {
- return addAssertion(new XtextDiagnosticAssertion(issueCode, false));
- }
-
- @Override
- protected void beforeApplyAssertions(final XtextTestSource testSource) {
- super.beforeApplyAssertions(testSource);
- EObject root = testSource.getModel();
- // Get all diagnostics of the current testing file
- EcoreUtil2.resolveLazyCrossReferences(root.eResource(), CancelIndicator.NullImpl);
- fileDiagnostics = validate(root);
- getUnexpectedDiagnostics().addAll(fileDiagnostics.getChildren());
- getUnexpectedResourceDiagnostics().addAll(root.eResource().getErrors());
- getUnexpectedResourceDiagnostics().addAll(root.eResource().getWarnings());
- }
-
- @Override
- protected String getAdditionalErrorMessageInformation() {
- return diagnosticsToString(fileDiagnostics, true);
- }
-
- @Override
- protected void afterValidate() {
- super.afterValidate();
- // Garbage collection
- getUnexpectedDiagnostics().clear();
- getUnexpectedResourceDiagnostics().clear();
- }
-
- /**
- * Assert that diagnosticList contains a diagnostic of the given issueCode.
- *
- * @param issueCode
- * the code of the issue to look for
- */
- protected void assertDiagnostic(final String issueCode) {
- assertDiagnostic(getPrimaryDiagnostics(), issueCode);
- }
-
- /**
- * Assert that the given EObject model contains a diagnostic of the given issueCode.
- *
- * @param model
- * the model in which to look for issues, may be {@code null}
- * @param issueCode
- * the code of the issue to look for
- */
- protected void assertDiagnostic(final EObject model, final String issueCode) {
- assertNotNull(model, "Issue with code '" + issueCode + "' cannot be found because the model is null");
- assertDiagnostic(getXtextTestUtil().getDiagnostician().validate(model), issueCode);
- }
-
- /**
- * Assert that diagnosticList does not contain a diagnostic of the given issueCode.
- *
- * @param issueCode
- * the code of the issue to look for
- */
- protected void assertNoDiagnostic(final String issueCode) {
- assertNoDiagnostic(getPrimaryDiagnostics(), issueCode);
- }
-
- /**
- * Assert that the given EObject model does not contain a diagnostic of the given issueCode.
- *
- * @param model
- * the model in which to look for issues, may be {@code null}
- * @param issueCode
- * the code of the issue to look for
- */
- protected void assertNoDiagnostic(final EObject model, final String issueCode) {
- assertNotNull(model, "Issue with code '" + issueCode + "' cannot be found because the model is null");
- assertNoDiagnostic(getXtextTestUtil().getDiagnostician().validate(model), issueCode);
- }
-
- /**
- * Assert that diagnosticList does not contain any diagnostic.
- */
- protected void assertNoDiagnostics() {
- assertNoDiagnostics(getPrimaryDiagnostics());
- }
-
- /**
- * Assert that the given EObject model does not contain any diagnostic.
- *
- * @param model
- * the model in which to look for issues, may be {@code null}
- */
- protected void assertNoDiagnostics(final EObject model) {
- assertNotNull(model, "Assertion cannot be checked because the model is null");
- assertNoDiagnostics(getXtextTestUtil().getDiagnostician().validate(model));
- }
-
- /**
- * Assert that diagnosticList contains a diagnostic with the given message.
- *
- * @param message
- * the message of the issue to look for
- */
- protected void assertDiagnosticMessage(final String message) {
- assertDiagnosticMessage(getPrimaryDiagnostics(), message);
- }
-
- /**
- * Assert that the given EObject model contains a diagnostic with the given message.
- *
- * @param model
- * the model in which to look for issues, may be {@code null}
- * @param message
- * the message of the issue to look for
- */
- protected void assertDiagnosticMessage(final EObject model, final String message) {
- assertNotNull(model, "Message '" + message + "' cannot be found because the model is null");
- assertDiagnosticMessage(getXtextTestUtil().getDiagnostician().validate(model), message);
- }
-
- /**
- * Assert that diagnosticList contains a diagnostic with the given message.
- *
- * @param diagnostics
- * the diagnostic to check for issues
- * @param message
- * the message of the issue to look for
- */
- private static void assertDiagnosticMessage(final Diagnostic diagnostics, final String message) {
- for (Diagnostic diagnostic : diagnostics.getChildren()) {
- if (diagnostic.getMessage().equals(message)) {
- return;
- }
- }
- fail("Issue with message ' " + message + "' not found");
- }
-
- /**
- * Assert that diagnosticList contains a diagnostic of the given issueCode.
- *
- * @param diagnostics
- * the diagnostic to check for issues
- * @param issueCode
- * the code of the issue to look for
- */
- private void assertDiagnostic(final Diagnostic diagnostics, final String issueCode) {
- for (Diagnostic diagnostic : diagnostics.getChildren()) {
- if (diagnostic instanceof AbstractValidationDiagnostic && ((AbstractValidationDiagnostic) diagnostic).getIssueCode().equals(issueCode)) {
- return;
- }
- }
- fail("Issue with code '" + issueCode + "' not found");
- }
-
- /**
- * Assert that diagnosticList contains a diagnostic of the given issueCode on a given EObject.
- * For performance reasons one can validate the root object and afterwards use this method
- * to check that a particular diagnostic exists on one of the child objects of the validated model.
- *
- * @param diagnostics
- * the diagnostic to check for issues
- * @param targetObject
- * the object that should have a diagnostic with the given issueCode
- * @param issueCode
- * the code of the issue to look for
- */
- protected void assertDiagnosticOnObject(final Diagnostic diagnostics, final EObject targetObject, final String issueCode) {
- for (Diagnostic diagnostic : diagnostics.getChildren()) {
- if (diagnostic instanceof AbstractValidationDiagnostic) {
- AbstractValidationDiagnostic avd = (AbstractValidationDiagnostic) diagnostic;
- if (avd.getSourceEObject() == targetObject && avd.getIssueCode().equals(issueCode)) {
- return;
- }
- }
- }
- fail("Issue with code '" + issueCode + "' not found");
- }
-
- /**
- * Assert that diagnosticList does not contain a diagnostic of the given issueCode.
- *
- * @param diagnostics
- * the diagnostic to check for issues
- * @param issueCode
- * the code of the issue to look for
- */
- private void assertNoDiagnostic(final Diagnostic diagnostics, final String issueCode) {
- for (Diagnostic diagnostic : diagnostics.getChildren()) {
- if (((AbstractValidationDiagnostic) diagnostic).getIssueCode().equals(issueCode)) {
- fail("Issue with code '" + issueCode + "' found");
- return;
- }
- }
- }
-
- /**
- * Assert that diagnosticList does not contain any diagnostic.
- *
- * @param diagnostics
- * the diagnostic to check for issues
- */
- private void assertNoDiagnostics(final Diagnostic diagnostics) {
- assertEquals(diagnostics.getCode(), Diagnostic.OK, "Diagnostics should be in OK state.");
- assertTrue(diagnostics.getChildren().isEmpty(), "There should be no diagnostics. Instead found " + diagnostics.getChildren().size());
- }
-
- /**
- * Assert no errors on resource exist.
- *
- * @param object
- * the object
- */
- public static void assertNoErrorsOnResource(final EObject object) {
- final EList errors = object.eResource().getErrors();
- if (!errors.isEmpty()) {
- fail(AbstractValidationTest.NO_ERRORS_FOUND_ON_RESOURCE_MESSAGE + "; found " + Lists.transform(errors, Resource.Diagnostic::getMessage)); //$NON-NLS-1$
- }
- }
-
- /**
- * Assert no errors on resource with the given message exist.
- *
- * @param object
- * the object
- * @param messages
- * the messages
- */
- public static void assertNoErrorsOnResource(final EObject object, final String... messages) {
- List messageList = Arrays.asList(messages);
- final EList errors = object.eResource().getErrors();
- for (String errorMessage : Lists.transform(errors, Resource.Diagnostic::getMessage)) {
- assertFalse(messageList.contains(errorMessage), NO_ERRORS_FOUND_ON_RESOURCE_MESSAGE + " with message '" + errorMessage + "'.");
- }
- }
-
- /**
- * Assert no linking errors on resource with the given message exist.
- *
- * @param object
- * the object
- * @param referenceType
- * the type of the referenced elements
- * @param referenceNames
- * the names of the referenced elements
- */
- public static void assertNoLinkingErrorsOnResource(final EObject object, final String referenceType, final String... referenceNames) {
- final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
- final List errorMessages = Lists.transform(linkingErrors, Resource.Diagnostic::getMessage);
- for (final String referenceName : referenceNames) {
- boolean found = false;
- for (final String errMessage : errorMessages) {
- if (errMessage.startsWith(referenceName)) {
- found = true;
- break;
- }
- }
- assertFalse(found, NLS.bind("Expecting no linking errors on resource for \"{0}\".", referenceName));
- }
- }
-
- /**
- * Assert linking errors on resource with the given message exist.
- *
- * @param object
- * the object
- * @param referenceType
- * the type of the referenced elements
- * @param referenceNames
- * the names of the referenced elements
- */
- public static void assertLinkingErrorsOnResourceExist(final EObject object, final String referenceType, final String... referenceNames) {
- final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
- final List errorMessages = Lists.transform(linkingErrors, Resource.Diagnostic::getMessage);
- for (final String referenceName : referenceNames) {
- boolean found = false;
- for (final String errMessage : errorMessages) {
- if (errMessage.contains(referenceName)) {
- found = true;
- break;
- }
- }
- assertTrue(found, NLS.bind("Expected linking error on \"{0}\" but could not find it", referenceName));
- }
- }
-
- /**
- * Expect the given linking error messages on the resource of the given model.
- *
- * @param object
- * the object, must not be {@code null}
- * @param errorStrings
- * the expected linking error error messages, must not be {@code null}
- */
- public static void assertLinkingErrorsWithCustomMessageOnResourceExist(final EObject object, final String... errorStrings) {
- final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
- final List errorMessages = Lists.transform(linkingErrors, Resource.Diagnostic::getMessage);
- for (final String s : errorStrings) {
- assertTrue(errorMessages.contains(s), NLS.bind("Expected linking error \"{0}\" but could not find it", s));
- }
- }
-
- /**
- * Assert no linking errors on resource with the given message exist.
- *
- * @param object
- * the object, must not be {@code null}
- * @param messages
- * the linking error messages, must not be {@code null}
- */
- public static void assertNoLinkingErrorsWithCustomMessageOnResource(final EObject object, final String... messages) {
- List messageList = Arrays.asList(messages);
- final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
- for (String errorMessage : Lists.transform(linkingErrors, Resource.Diagnostic::getMessage)) {
- assertFalse(messageList.contains(errorMessage), NLS.bind("Expecting no linking errors on resource with message \"{0}\".", errorMessage));
- }
- }
-
- /**
- * Expect given error messages on the resource of given model.
- *
- * @param object
- * the object
- * @param errorStrings
- * the error strings
- */
- public static void assertErrorsOnResourceExist(final EObject object, final String... errorStrings) {
- final EList errors = object.eResource().getErrors();
- final List errorMessages = Lists.transform(errors, Resource.Diagnostic::getMessage);
- for (final String s : errorStrings) {
- assertTrue(errorMessages.contains(s), NLS.bind("Expected error \"{0}\" but could not find it", s));
- }
- }
-
- /**
- * Validates if there is a syntax error present in the source content.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, must not be {@code null}
- * @param sourceContent
- * source, must not be {@code null}
- */
- protected void assertNoSyntaxErrorsOnResource(final String sourceFileName, final CharSequence sourceContent) {
- final XtextTestSource testSource = createTestSource(sourceFileName, sourceContent.toString());
- final List errors = testSource.getModel().eResource().getErrors().stream().filter(error -> error instanceof XtextSyntaxDiagnostic).collect(Collectors.toList());
- if (!errors.isEmpty()) {
- StringBuilder sb = new StringBuilder("Syntax error is present in the test source.\nList of all found syntax errors:");
- errors.forEach(err -> sb.append("\n\t ").append(err.getMessage()));
- throw new AssertionError(sb.toString());
- }
- }
-
- /**
- * Memorize the position and issue code of each resource error that appears in the file.
- *
- * @param root
- * root node of the model to be analyzed
- */
- protected void memorizeUnexpectedResourceErrors() {
- for (Resource.Diagnostic diagnostic : getUnexpectedResourceDiagnostics()) {
- if (diagnostic instanceof AbstractDiagnostic) {
- AbstractDiagnostic diag = (AbstractDiagnostic) diagnostic;
- // Create error message
- StringBuilder sb = new StringBuilder(35);
- sb.append("Unexpected diagnostic found. Code '");
- sb.append(diag.getCode());
- sb.append(DOT_AND_LINEBREAK);
- // Retrieve the position and add the error
- memorizeErrorOnPosition(diag.getOffset(), sb.toString());
- } else {
- // Create error message
- StringBuilder sb = new StringBuilder(30);
- sb.append("Unexpected diagnostic found. '");
- sb.append(diagnostic.toString());
- sb.append(DOT_AND_LINEBREAK);
- // Add error message
- memorizeErrorOnPosition(0, sb.toString());
- }
- }
- }
-
- /**
- * Memorize the position and issue code of each unexpected diagnostic that appears in the file.
- * A diagnostic is considered as expected if a marker with the issue code in the test file was set.
- */
- protected void memorizeUnexpectedErrors() {
- for (Diagnostic diagnostic : getUnexpectedDiagnostics()) {
- if (diagnostic instanceof AbstractValidationDiagnostic) {
- AbstractValidationDiagnostic avd = (AbstractValidationDiagnostic) diagnostic;
- // Create error message
- StringBuilder sb = new StringBuilder(30);
- sb.append("Unexpected issue found. Code '");
- sb.append(avd.getIssueCode());
- sb.append(DOT_AND_LINEBREAK);
- // Retrieve the position and add the error
- if (avd instanceof FeatureBasedDiagnostic && ((FeatureBasedDiagnostic) avd).getFeature() != null) {
- List nodes = NodeModelUtils.findNodesForFeature(avd.getSourceEObject(), ((FeatureBasedDiagnostic) avd).getFeature());
- if (nodes.isEmpty()) {
- INode node = NodeModelUtils.getNode(avd.getSourceEObject());
- memorizeErrorOnPosition(getXtextTestUtil().findFirstNonHiddenLeafNode(node).getTotalOffset(), sb.toString());
- } else {
- for (INode node : nodes) {
- memorizeErrorOnPosition(getXtextTestUtil().findFirstNonHiddenLeafNode(node).getTotalOffset(), sb.toString());
- }
- }
- } else if (avd instanceof RangeBasedDiagnostic) {
- memorizeErrorOnPosition(((RangeBasedDiagnostic) avd).getOffset(), sb.toString());
- } else {
- memorizeErrorOnPosition(NodeModelUtils.getNode(avd.getSourceEObject()).getTotalOffset(), sb.toString());
- }
- } else {
- // Create error message
- StringBuilder sb = new StringBuilder(30);
- sb.append("Unexpected diagnostic found. '");
- sb.append(diagnostic.toString());
- sb.append(DOT_AND_LINEBREAK);
- // Add error message
- memorizeErrorOnPosition(0, sb.toString());
- }
- }
- }
-
- /**
- * Strictly validates a source given by a file name and content.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, must not be {@code null}
- * @param sourceType
- * defines if the source is a kernel or customer source, must not be {@code null}
- * @param sourceContent
- * source, must not be {@code null}
- */
- protected void validateStrictly(final String sourceFileName, final TestSourceType sourceType, final CharSequence sourceContent) {
- XtextTestSource testSource = processMarkers(sourceFileName, sourceType, sourceContent);
- memorizeUnexpectedErrors();
- memorizeUnexpectedResourceErrors();
- processErrorsFound(testSource.getContent());
- afterValidate();
- }
-
- /**
- * Strictly validate a kernel source given by a {@link Pair} of file name and content.
- * All not expected diagnostics are considered as an error.
- *
- * @param sourceFileNameAndContent
- * the file name and content, given as the key and value of the pair, respectively, must not be {@code null}
- */
- protected void validateKernelSourceStrictly(final Pair sourceFileNameAndContent) {
- validateKernelSourceStrictly(sourceFileNameAndContent.getKey(), sourceFileNameAndContent.getValue());
- }
-
- /**
- * Strictly validate a customer source given by a {@link Pair} of file name and content.
- * All not expected diagnostics are considered as an error.
- *
- * @param sourceFileNameAndContent
- * the file name and content, given as the key and value of the pair, respectively, must not be {@code null}
- */
- protected void validateCustomerSourceStrictly(final Pair sourceFileNameAndContent) {
- validateCustomerSourceStrictly(sourceFileNameAndContent.getKey(), sourceFileNameAndContent.getValue());
- }
-
- /**
- * Strictly validate a kernel source given by a file name and content.
- * All not expected diagnostics are considered as an error.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, must not be {@code null}
- * @param sourceContent
- * source, must not be {@code null}
- */
- protected void validateKernelSourceStrictly(final String sourceFileName, final CharSequence sourceContent) {
- validateStrictly(sourceFileName, TestSourceType.CLIENT_ALL, sourceContent);
- }
-
- /**
- * Strictly validate a customer source given by a file name and content.
- * All not expected diagnostics are considered as an error.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, must not be {@code null}
- * @param sourceContent
- * source, must not be {@code null}
- */
- protected void validateCustomerSourceStrictly(final String sourceFileName, final CharSequence sourceContent) {
- validateStrictly(sourceFileName, TestSourceType.CLIENT_CUSTOMER, sourceContent);
- }
-}
+/*******************************************************************************
+ * 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 static org.eclipse.xtext.validation.ValidationMessageAcceptor.INSIGNIFICANT_INDEX;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import org.eclipse.emf.common.util.BasicDiagnostic;
+import org.eclipse.emf.common.util.Diagnostic;
+import org.eclipse.emf.common.util.EList;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.xtext.EcoreUtil2;
+import org.eclipse.xtext.diagnostics.AbstractDiagnostic;
+import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic;
+import org.eclipse.xtext.nodemodel.ICompositeNode;
+import org.eclipse.xtext.nodemodel.INode;
+import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.resource.XtextSyntaxDiagnostic;
+import org.eclipse.xtext.util.CancelIndicator;
+import org.eclipse.xtext.validation.AbstractValidationDiagnostic;
+import org.eclipse.xtext.validation.FeatureBasedDiagnostic;
+import org.eclipse.xtext.validation.RangeBasedDiagnostic;
+import org.eclipse.xtext.xbase.lib.Pair;
+
+import com.avaloq.tools.ddk.xtext.test.XtextTestSource;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.inject.Provider;
+
+
+/**
+ * Base class for validation tests.
+ */
+@SuppressWarnings({"nls", "PMD.ExcessiveClassLength"})
+// CHECKSTYLE:CHECK-OFF MultipleStringLiterals
+// CHECKSTYLE:OFF MagicNumber
+public abstract class AbstractValidationTest extends AbstractXtextMarkerBasedTest {
+
+ public static final int NO_ERRORS = 0;
+
+ static final String NO_ERRORS_FOUND_ON_RESOURCE_MESSAGE = "Expecting no errors on resource";
+
+ private static final int SEVERITY_UNDEFINED = -1;
+ private static final Map CODE_TO_NAME = ImmutableMap.of(Diagnostic.INFO, "INFO", Diagnostic.WARNING, "WARNING", Diagnostic.ERROR, "ERROR");
+
+ private static final String LINE_BREAK = "\n";
+ private static final String DOT_AND_LINEBREAK = "'." + LINE_BREAK;
+
+ /**
+ * All diagnostics of the current testing file.
+ */
+ private Diagnostic fileDiagnostics;
+
+ /**
+ * During validation of a source we monitor diagnostics, that are found in the source but were not expected by the test.
+ * If the validation test is strict, then we will display these unexpected diagnostics as test error.
+ */
+ private final Set unexpectedDiagnostics = Sets.newLinkedHashSet();
+ private final Set unexpectedResourceDiagnostics = Sets.newLinkedHashSet();
+
+ /**
+ * validation results calculated during test setUp.
+ *
+ * @return the diagnostic for the primary test source file
+ */
+ private Diagnostic getPrimaryDiagnostics() {
+ Object obj = getTestInformation().getTestObject(Diagnostic.class);
+ assertNotNull(obj, "getPrimaryDiagnostics(): Diagnostics of primary source not null.");
+ return (Diagnostic) obj;
+ }
+
+ /**
+ * Returns the unexpectedDiagnostics.
+ *
+ * @return the unexpectedDiagnostics
+ */
+ protected Set getUnexpectedDiagnostics() {
+ return unexpectedDiagnostics;
+ }
+
+ /**
+ * Returns the unexpectedDiagnostics.
+ *
+ * @return the unexpectedDiagnostics
+ */
+ protected Set getUnexpectedResourceDiagnostics() {
+ return unexpectedResourceDiagnostics;
+ }
+
+ /**
+ * Assertion testing for {@link AbstractValidationDiagnostic validation issues} at a given source position.
+ */
+ protected class XtextDiagnosticAssertion extends AbstractModelAssertion {
+
+ /** Issue code of the diagnostic. */
+ private final String issueCode;
+ /** Issue message of the diagnostic. */
+ private final String message;
+ /**
+ * Indicates whether the assertion must find the issue.
+ * Assertion creates an error if the existence of issue code for the target eobject doesn't correspond to the value of issueMustBeFound.
+ */
+ private final boolean issueMustBeFound;
+
+ private final int expectedSeverity;
+
+ protected XtextDiagnosticAssertion(final String issueCode, final boolean issueMustBeFound) {
+ this(issueCode, issueMustBeFound, SEVERITY_UNDEFINED, null);
+ }
+
+ protected XtextDiagnosticAssertion(final String issueCode, final boolean issueMustBeFound, final int severity, final String message) {
+ this.issueCode = issueCode;
+ this.issueMustBeFound = issueMustBeFound;
+ this.expectedSeverity = severity;
+ this.message = message;
+ }
+
+ /**
+ * Check if the given issue code is found among issue codes for the object, located at the given position.
+ *
+ * @param root
+ * root object of the document
+ * @param pos
+ * position to locate the target object
+ */
+ @Override
+ public void apply(final EObject root, final Integer pos) {
+ final Diagnostic diagnostics = validate(root);
+ final BasicDiagnostic diagnosticsOnTargetPosition = new BasicDiagnostic();
+ boolean issueFound = false;
+ int actualSeverity = SEVERITY_UNDEFINED;
+ boolean expectedSeverityMatches = false;
+ boolean expectedMessageMatches = false;
+ String actualMessage = "";
+
+ for (AbstractValidationDiagnostic avd : Iterables.filter(diagnostics.getChildren(), AbstractValidationDiagnostic.class)) {
+ if (diagnosticPositionEquals(pos, avd)) {
+ // Add issue to the list of issues at the given position
+ diagnosticsOnTargetPosition.add(avd);
+ if (avd.getIssueCode().equals(issueCode)) {
+ issueFound = true;
+ actualSeverity = avd.getSeverity();
+ // True if the expected severity is not set, or if matches with the actual one
+ expectedSeverityMatches = expectedSeverity == SEVERITY_UNDEFINED || expectedSeverity == actualSeverity;
+ actualMessage = avd.getMessage();
+ // True if message matches with actual message or message is null
+ expectedMessageMatches = message == null || actualMessage.equals(message);
+ if (issueMustBeFound) {
+ // Remove the diagnostic from the list of non-expected diagnostics
+ getUnexpectedDiagnostics().remove(avd);
+ // Don't need to display error messages
+ if (expectedSeverityMatches && expectedMessageMatches) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // Create error message
+ createErrorMessage(pos, diagnosticsOnTargetPosition, issueFound, expectedSeverityMatches, actualSeverity, expectedMessageMatches, actualMessage);
+ }
+
+ /**
+ * Create an error message (if needed) based on the given input parameters.
+ *
+ * @param pos
+ * position in the source to associate the message with
+ * @param diagnosticsOnTargetPosition
+ * diagnostics on the specifies position
+ * @param issueFound
+ * specifies whether an issue has been found at the given position
+ * @param expectedSeverityMatches
+ * true if expected severity equals actual one, false otherwise
+ * @param actualSeverity
+ * actual severity
+ * @param expectedMessageMatches
+ * expected message matches
+ * @param actualMessage
+ * actual message
+ */
+ private void createErrorMessage(final Integer pos, final BasicDiagnostic diagnosticsOnTargetPosition, final boolean issueFound, final boolean expectedSeverityMatches, final int actualSeverity, final boolean expectedMessageMatches, final String actualMessage) {
+ StringBuilder errorMessage = new StringBuilder(180);
+ if (issueMustBeFound && !issueFound) {
+ errorMessage.append("Expected issue not found. Code '").append(issueCode).append('\n');
+ } else if (!issueMustBeFound && issueFound) {
+ errorMessage.append("There should be no issue with the code '").append(issueCode).append(DOT_AND_LINEBREAK);
+ }
+ if (issueFound && !expectedMessageMatches) {
+ errorMessage.append("Expected message does not match. Expected: '").append(message).append("', Actual: '").append(actualMessage).append('\n');
+ }
+ // If the expected issue has been found, but the actual severity does not match with expected one
+ if (issueMustBeFound && issueFound && !expectedSeverityMatches) {
+ errorMessage.append("Severity does not match. Expected: ").append(CODE_TO_NAME.get(expectedSeverity)).append(". Actual: ").append(CODE_TO_NAME.get(actualSeverity)).append(".\n");
+ }
+ // Memorize error message
+ if (errorMessage.length() > 0) {
+ if (!diagnosticsOnTargetPosition.getChildren().isEmpty()) {
+ errorMessage.append(" All issues at this position:\n");
+ errorMessage.append(diagnosticsToString(diagnosticsOnTargetPosition, false));
+ }
+ memorizeErrorOnPosition(pos, errorMessage.toString());
+ }
+ }
+
+ /**
+ * Compare if the position of the given diagnostic equals to the given position in text.
+ *
+ * @param pos
+ * position in text
+ * @param avd
+ * diagnostic that we check, if it has the same position as the given position in text
+ * @return
+ * TRUE if diagnostic has the same position as the given one, FALSE otherwise.
+ */
+ protected boolean diagnosticPositionEquals(final Integer pos, final AbstractValidationDiagnostic avd) {
+ if (avd instanceof FeatureBasedDiagnostic && ((FeatureBasedDiagnostic) avd).getFeature() != null) {
+ List nodes = NodeModelUtils.findNodesForFeature(avd.getSourceEObject(), ((FeatureBasedDiagnostic) avd).getFeature());
+ if (nodes.isEmpty()) {
+ INode node = NodeModelUtils.getNode(avd.getSourceEObject());
+ INode firstNonHiddenLeafNode = getXtextTestUtil().findFirstNonHiddenLeafNode(node);
+ if (firstNonHiddenLeafNode == null) {
+ return issueMustBeFound;
+ } else if (firstNonHiddenLeafNode.getTotalOffset() == pos) {
+ return true;
+ }
+ } else {
+ int avdIndex = ((FeatureBasedDiagnostic) avd).getIndex();
+ for (int i = 0; i < nodes.size(); i++) {
+ if (avdIndex == INSIGNIFICANT_INDEX || avdIndex == i) {
+ INode firstNonHiddenLeafNode = getXtextTestUtil().findFirstNonHiddenLeafNode(nodes.get(i));
+ if (firstNonHiddenLeafNode == null) {
+ return issueMustBeFound;
+ } else if (firstNonHiddenLeafNode.getTotalOffset() == pos) {
+ return true;
+ }
+ }
+ }
+ }
+ } else if (avd instanceof RangeBasedDiagnostic) {
+ if (((RangeBasedDiagnostic) avd).getOffset() == pos) {
+ return true;
+ }
+ } else {
+ INode node = NodeModelUtils.getNode(avd.getSourceEObject());
+ INode firstNonHiddenLeafNode = getXtextTestUtil().findFirstNonHiddenLeafNode(node);
+ if (firstNonHiddenLeafNode == null) {
+ return issueMustBeFound;
+ } else if (firstNonHiddenLeafNode.getTotalOffset() == pos) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Assertion testing for {@link AbstractValidationDiagnostic validation issues} at a given source position.
+ */
+ private class ResourceDiagnosticAssertion extends AbstractModelAssertion {
+
+ /** Issue code of the diagnostic. */
+ private final String issueCode;
+ /** Issue message of the diagnostic. */
+ private final String message;
+ /**
+ * Indicates whether the assertion must find the issue.
+ * Assertion creates an error if the existence of issue code for the target eobject doesn't correspond to the value of issueMustBeFound.
+ */
+ private final boolean issueMustBeFound;
+
+ private final int expectedSeverity;
+
+ protected ResourceDiagnosticAssertion(final String issueCode, final boolean issueMustBeFound, final int severity, final String message) {
+ this.issueCode = issueCode;
+ this.issueMustBeFound = issueMustBeFound;
+ this.expectedSeverity = severity;
+ this.message = message;
+ }
+
+ /**
+ * Check if the given issue code is found among issue codes for the object, located at the given position.
+ *
+ * @param root
+ * root object of the document
+ * @param pos
+ * position to locate the target object
+ */
+ @Override
+ public void apply(final EObject root, final Integer pos) {
+ Iterable diagnostics = null;
+ switch (expectedSeverity) {
+ case Diagnostic.ERROR:
+ diagnostics = root.eResource().getErrors();
+ break;
+ case Diagnostic.WARNING:
+ diagnostics = root.eResource().getWarnings();
+ break;
+ case SEVERITY_UNDEFINED:
+ diagnostics = Iterables.concat(root.eResource().getErrors(), root.eResource().getWarnings());
+ break;
+ }
+ final List diagnosticsOnTargetPosition = Lists.newArrayList();
+ boolean issueFound = false;
+ int actualSeverity = expectedSeverity;
+ boolean expectedMessageMatches = false;
+ String actualMessage = "";
+
+ for (AbstractDiagnostic diag : Iterables.filter(diagnostics, AbstractDiagnostic.class)) {
+ if (diagnosticPositionEquals(pos, diag)) {
+ // Add issue to the list of issues at the given position
+ diagnosticsOnTargetPosition.add(diag);
+ if (diag.getCode() != null && diag.getCode().equals(issueCode)) {
+ issueFound = true;
+ if (expectedSeverity == SEVERITY_UNDEFINED) {
+ actualSeverity = root.eResource().getErrors().contains(diag) ? Diagnostic.ERROR : Diagnostic.WARNING;
+ }
+ actualMessage = diag.getMessage();
+ // True if message matches with actual message or message is null
+ expectedMessageMatches = message == null || actualMessage.equals(message);
+ // Don't need to display error messages
+ if (issueMustBeFound) {
+ // Remove the diagnostic from the list of non-expected diagnostics
+ getUnexpectedResourceDiagnostics().remove(diag);
+ // Don't need to display error messages
+ if (expectedMessageMatches) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ // Create error message
+ createErrorMessage(pos, diagnosticsOnTargetPosition, issueFound, true, actualSeverity, expectedMessageMatches, actualMessage);
+ }
+
+ /**
+ * Create an error message (if needed) based on the given input parameters.
+ *
+ * @param pos
+ * position in the source to associate the message with
+ * @param diagnosticsOnTargetPosition
+ * diagnostics on the specifies position
+ * @param issueFound
+ * specifies whether an issue has been found at the given position
+ * @param expectedSeverityMatches
+ * true if expected severity equals actual one, false otherwise
+ * @param actualSeverity
+ * actual severity
+ * @param expectedMessageMatches
+ * expected message matches
+ * @param actualMessage
+ * actual message
+ */
+ private void createErrorMessage(final Integer pos, final List diagnosticsOnTargetPosition, final boolean issueFound, final boolean expectedSeverityMatches, final int actualSeverity, final boolean expectedMessageMatches, final String actualMessage) {
+ StringBuilder errorMessage = new StringBuilder(175);
+ if (issueMustBeFound && !issueFound) {
+ errorMessage.append("Expected issue not found. Code '").append(issueCode).append('\n');
+ } else if (!issueMustBeFound && issueFound) {
+ errorMessage.append("There should be no issue with the code '").append(issueCode).append(DOT_AND_LINEBREAK);
+ }
+ if (issueFound && !expectedMessageMatches) {
+ errorMessage.append("Expected message does not match. Expected: '").append(message).append("', Actual: '").append(actualMessage).append('\n');
+ }
+ // If the expected issue has been found, but the actual severity does not match with expected one
+ if (issueMustBeFound && issueFound && !expectedSeverityMatches) {
+ errorMessage.append("Severity does not match. Expected: ").append(CODE_TO_NAME.get(expectedSeverity)).append(". Actual: ").append(CODE_TO_NAME.get(actualSeverity)).append(".\n");
+ }
+ // Memorize error message
+ if (errorMessage.length() > 0) {
+ if (!diagnosticsOnTargetPosition.isEmpty()) {
+ errorMessage.append(" All issues at this position:\n");
+ errorMessage.append(diagnosticsToString(diagnosticsOnTargetPosition, false));
+ }
+ memorizeErrorOnPosition(pos, errorMessage.toString());
+ }
+ }
+
+ /**
+ * Compare if the position of the given diagnostic equals to the given position in text.
+ *
+ * @param pos
+ * position in text
+ * @param diagnostic
+ * diagnostic that we check, if it has the same position as the given position in text
+ * @return
+ * {@code true} if diagnostic has the same position as the given one, {@code false} otherwise.
+ */
+ private boolean diagnosticPositionEquals(final Integer pos, final AbstractDiagnostic diagnostic) {
+ return diagnostic.getOffset() == pos;
+ }
+ }
+
+ /**
+ * Get a cached version of an object associated with the root object for a given key.
+ *
+ * @param
+ * type of the associated object
+ * @param root
+ * root EObject
+ * @param key
+ * key identifying the type of the associated object
+ * @param provider
+ * provider to deliver an object if there is no cached version
+ * @return
+ * cached version of the associated object
+ */
+ protected T getCached(final EObject root, final String key, final Provider provider) {
+ XtextResource res = (XtextResource) root.eResource();
+ return res.getCache().get(key, res, provider);
+ }
+
+ /**
+ * Validate the model.
+ *
+ * @param root
+ * root EObject to validate
+ * @return
+ * validation results
+ */
+ protected Diagnostic validate(final EObject root) {
+ return getCached(root, "DIAGNOSTIC", () -> getXtextTestUtil().getDiagnostician().validate(root));
+ }
+
+ /**
+ * Display the path from root object to the target EObject.
+ *
+ * @param eObject
+ * object to display the object path for
+ * @param offset
+ * string offset that is added in the beginning of each line
+ * @return
+ * object hierarchy as string (each object on a single line)
+ */
+ private String pathFromRootAsString(final EObject eObject, final String offset) {
+ List hierarchy = Lists.newLinkedList();
+
+ EObject currentObject = eObject;
+ while (currentObject != null) {
+ hierarchy.add(0, offset + currentObject.toString());
+ currentObject = currentObject.eContainer();
+ }
+
+ return String.join("\n", hierarchy);
+ }
+
+ /**
+ * Persist list diagnostics into string to display the list of issue codes.
+ *
+ * @param diagnostics
+ * list of diagnostics
+ * @param displayPathToTargetObject
+ * if true, the path through the object hierarchy is printed out up to the root node
+ * @return
+ * string with list of issue codes, separated with a line break
+ */
+ // TODO (ACF-4153) generalize for all kinds of errors and move to AbstractXtextTest
+ private String diagnosticsToString(final Diagnostic diagnostics, final boolean displayPathToTargetObject) {
+ StringBuilder sb = new StringBuilder();
+ for (Diagnostic diagnostic : diagnostics.getChildren()) {
+ if (diagnostic instanceof AbstractValidationDiagnostic) {
+ AbstractValidationDiagnostic avd = (AbstractValidationDiagnostic) diagnostic;
+ sb.append(" ");
+ sb.append(avd.getIssueCode());
+ if (displayPathToTargetObject) {
+ sb.append(" at line: ");
+ ICompositeNode compositeNode = NodeModelUtils.findActualNodeFor(avd.getSourceEObject());
+ if (compositeNode != null) {
+ sb.append(compositeNode.getStartLine());
+ } else {
+ sb.append("Unknown");
+ }
+ sb.append(" on \n");
+ sb.append(pathFromRootAsString(avd.getSourceEObject(), " "));
+ }
+ sb.append(LINE_BREAK);
+ }
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Persist list diagnostics into string to display the list of issue codes.
+ *
+ * @param diagnostics
+ * list of diagnostics
+ * @param displayPathToTargetObject
+ * if true, the path through the object hierarchy is printed out up to the root node
+ * @return
+ * string with list of issue codes, separated with a line break
+ */
+ // TODO (ACF-4153) generalize for all kinds of errors and move to AbstractXtextTest
+ private String diagnosticsToString(final List diagnostics, final boolean displayPathToTargetObject) {
+ StringBuilder sb = new StringBuilder(25);
+ for (Resource.Diagnostic diagnostic : diagnostics) {
+ if (diagnostic instanceof AbstractDiagnostic) {
+ AbstractDiagnostic diag = (AbstractDiagnostic) diagnostic;
+ sb.append(" ");
+ sb.append(diag.getCode());
+ if (displayPathToTargetObject) {
+ sb.append(" at line: ");
+ sb.append(diag.getLine());
+ sb.append(" on \n");
+ sb.append(" ");
+ sb.append(diag.getUriToProblem());
+ }
+ sb.append(LINE_BREAK);
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ protected void beforeAllTests() {
+ super.beforeAllTests();
+ if (getTestSource() != null) {
+ Diagnostic primaryDiagnostics = getXtextTestUtil().getDiagnostician().validate(getSemanticModel());
+ getTestInformation().putTestObject(Diagnostic.class, primaryDiagnostics);
+ }
+ }
+
+ /**
+ * Register a new validation marker with the given issue code. Expects an info.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String info(final String issueCode) {
+ return info(issueCode, null);
+ }
+
+ /**
+ * Register a new validation marker with the given issue code and message. Expects an info.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @param message
+ * the expected issue message
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String info(final String issueCode, final String message) {
+ return addAssertion(new XtextDiagnosticAssertion(issueCode, true, Diagnostic.INFO, message));
+ }
+
+ /**
+ * Register a new validation marker with the given issue code. Expects a warning if the condition is {@code true}, no diagnostic otherwise.
+ *
+ * @param condition
+ * the condition when the marker is expected
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String warningIf(final boolean condition, final String issueCode) {
+ if (condition) {
+ return warning(issueCode);
+ } else {
+ return noDiagnostic(issueCode);
+ }
+ }
+
+ /**
+ * Register a new validation marker with the given issue code. Expects a warning.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String warning(final String issueCode) {
+ return warning(issueCode, null);
+ }
+
+ /**
+ * Register a new validation marker with the given issue code and message. Expects a warning.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @param message
+ * the expected issue message
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String warning(final String issueCode, final String message) {
+ return addAssertion(new XtextDiagnosticAssertion(issueCode, true, Diagnostic.WARNING, message));
+ }
+
+ /**
+ * Register a new validation marker with the given issue code. Expects an error if the condition is {@code true}, no diagnostic otherwise.
+ *
+ * @param condition
+ * the condition when the marker is expected
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String errorIf(final boolean condition, final String issueCode) {
+ if (condition) {
+ return error(issueCode);
+ } else {
+ return noDiagnostic(issueCode);
+ }
+ }
+
+ /**
+ * Register a new validation marker with the given issue code. Expects an error.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String error(final String issueCode) {
+ return error(issueCode, null);
+ }
+
+ /**
+ * Register a new validation marker with the given issue code and message. Expects an error.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @param message
+ * the expected issue message
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String error(final String issueCode, final String message) {
+ return addAssertion(new XtextDiagnosticAssertion(issueCode, true, Diagnostic.ERROR, message));
+ }
+
+ /**
+ * Register a new validation marker with the given issue code.
+ * The issue is expected to be found in the test file.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String diagnostic(final String issueCode) {
+ return diagnostic(issueCode, null);
+ }
+
+ /**
+ * Register a new validation marker with the given issue code and message.
+ * The issue and message are expected to be found in the test file.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @param message
+ * the expected issue message
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String diagnostic(final String issueCode, final String message) {
+ return addAssertion(new XtextDiagnosticAssertion(issueCode, true, SEVERITY_UNDEFINED, message));
+ }
+
+ /**
+ * Register a new linking error validation marker.
+ * The issue is expected to be found in the test file.
+ *
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String linkingError() {
+ return linkingError(null);
+ }
+
+ /**
+ * Register a new linking error validation marker with the given message.
+ * The issue is expected to be found in the test file.
+ *
+ * @param message
+ * issuethe expected issue message
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String linkingError(final String message) {
+ return addAssertion(new ResourceDiagnosticAssertion(org.eclipse.xtext.diagnostics.Diagnostic.LINKING_DIAGNOSTIC, true, Diagnostic.ERROR, message));
+ }
+
+ /**
+ * Register a new resource validation marker with the given issue code and message.
+ * The issue is expected to be found in the test file.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @param message
+ * the expected issue message
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String resourceDiagnostic(final String issueCode, final String message) {
+ return addAssertion(new ResourceDiagnosticAssertion(issueCode, true, Diagnostic.ERROR, message));
+ }
+
+ /**
+ * Register a new validation marker with the given issue code.
+ * The issue is expected NOT to be found in the test file.
+ *
+ * @param issueCode
+ * issue code (usually found as static constant of the JavaValidator class of the DSL being tested)
+ * @return
+ * unique marker that can be used in the input string to mark a position that should be validated
+ */
+ protected String noDiagnostic(final String issueCode) {
+ return addAssertion(new XtextDiagnosticAssertion(issueCode, false));
+ }
+
+ @Override
+ protected void beforeApplyAssertions(final XtextTestSource testSource) {
+ super.beforeApplyAssertions(testSource);
+ EObject root = testSource.getModel();
+ // Get all diagnostics of the current testing file
+ EcoreUtil2.resolveLazyCrossReferences(root.eResource(), CancelIndicator.NullImpl);
+ fileDiagnostics = validate(root);
+ getUnexpectedDiagnostics().addAll(fileDiagnostics.getChildren());
+ getUnexpectedResourceDiagnostics().addAll(root.eResource().getErrors());
+ getUnexpectedResourceDiagnostics().addAll(root.eResource().getWarnings());
+ }
+
+ @Override
+ protected String getAdditionalErrorMessageInformation() {
+ return diagnosticsToString(fileDiagnostics, true);
+ }
+
+ @Override
+ protected void afterValidate() {
+ super.afterValidate();
+ // Garbage collection
+ getUnexpectedDiagnostics().clear();
+ getUnexpectedResourceDiagnostics().clear();
+ }
+
+ /**
+ * Assert that diagnosticList contains a diagnostic of the given issueCode.
+ *
+ * @param issueCode
+ * the code of the issue to look for
+ */
+ protected void assertDiagnostic(final String issueCode) {
+ assertDiagnostic(getPrimaryDiagnostics(), issueCode);
+ }
+
+ /**
+ * Assert that the given EObject model contains a diagnostic of the given issueCode.
+ *
+ * @param model
+ * the model in which to look for issues, may be {@code null}
+ * @param issueCode
+ * the code of the issue to look for
+ */
+ protected void assertDiagnostic(final EObject model, final String issueCode) {
+ assertNotNull(model, "Issue with code '" + issueCode + "' cannot be found because the model is null");
+ assertDiagnostic(getXtextTestUtil().getDiagnostician().validate(model), issueCode);
+ }
+
+ /**
+ * Assert that diagnosticList does not contain a diagnostic of the given issueCode.
+ *
+ * @param issueCode
+ * the code of the issue to look for
+ */
+ protected void assertNoDiagnostic(final String issueCode) {
+ assertNoDiagnostic(getPrimaryDiagnostics(), issueCode);
+ }
+
+ /**
+ * Assert that the given EObject model does not contain a diagnostic of the given issueCode.
+ *
+ * @param model
+ * the model in which to look for issues, may be {@code null}
+ * @param issueCode
+ * the code of the issue to look for
+ */
+ protected void assertNoDiagnostic(final EObject model, final String issueCode) {
+ assertNotNull(model, "Issue with code '" + issueCode + "' cannot be found because the model is null");
+ assertNoDiagnostic(getXtextTestUtil().getDiagnostician().validate(model), issueCode);
+ }
+
+ /**
+ * Assert that diagnosticList does not contain any diagnostic.
+ */
+ protected void assertNoDiagnostics() {
+ assertNoDiagnostics(getPrimaryDiagnostics());
+ }
+
+ /**
+ * Assert that the given EObject model does not contain any diagnostic.
+ *
+ * @param model
+ * the model in which to look for issues, may be {@code null}
+ */
+ protected void assertNoDiagnostics(final EObject model) {
+ assertNotNull(model, "Assertion cannot be checked because the model is null");
+ assertNoDiagnostics(getXtextTestUtil().getDiagnostician().validate(model));
+ }
+
+ /**
+ * Assert that diagnosticList contains a diagnostic with the given message.
+ *
+ * @param message
+ * the message of the issue to look for
+ */
+ protected void assertDiagnosticMessage(final String message) {
+ assertDiagnosticMessage(getPrimaryDiagnostics(), message);
+ }
+
+ /**
+ * Assert that the given EObject model contains a diagnostic with the given message.
+ *
+ * @param model
+ * the model in which to look for issues, may be {@code null}
+ * @param message
+ * the message of the issue to look for
+ */
+ protected void assertDiagnosticMessage(final EObject model, final String message) {
+ assertNotNull(model, "Message '" + message + "' cannot be found because the model is null");
+ assertDiagnosticMessage(getXtextTestUtil().getDiagnostician().validate(model), message);
+ }
+
+ /**
+ * Assert that diagnosticList contains a diagnostic with the given message.
+ *
+ * @param diagnostics
+ * the diagnostic to check for issues
+ * @param message
+ * the message of the issue to look for
+ */
+ private static void assertDiagnosticMessage(final Diagnostic diagnostics, final String message) {
+ for (Diagnostic diagnostic : diagnostics.getChildren()) {
+ if (diagnostic.getMessage().equals(message)) {
+ return;
+ }
+ }
+ fail("Issue with message ' " + message + "' not found");
+ }
+
+ /**
+ * Assert that diagnosticList contains a diagnostic of the given issueCode.
+ *
+ * @param diagnostics
+ * the diagnostic to check for issues
+ * @param issueCode
+ * the code of the issue to look for
+ */
+ private void assertDiagnostic(final Diagnostic diagnostics, final String issueCode) {
+ for (Diagnostic diagnostic : diagnostics.getChildren()) {
+ if (diagnostic instanceof AbstractValidationDiagnostic && ((AbstractValidationDiagnostic) diagnostic).getIssueCode().equals(issueCode)) {
+ return;
+ }
+ }
+ fail("Issue with code '" + issueCode + "' not found");
+ }
+
+ /**
+ * Assert that diagnosticList contains a diagnostic of the given issueCode on a given EObject.
+ * For performance reasons one can validate the root object and afterwards use this method
+ * to check that a particular diagnostic exists on one of the child objects of the validated model.
+ *
+ * @param diagnostics
+ * the diagnostic to check for issues
+ * @param targetObject
+ * the object that should have a diagnostic with the given issueCode
+ * @param issueCode
+ * the code of the issue to look for
+ */
+ protected void assertDiagnosticOnObject(final Diagnostic diagnostics, final EObject targetObject, final String issueCode) {
+ for (Diagnostic diagnostic : diagnostics.getChildren()) {
+ if (diagnostic instanceof AbstractValidationDiagnostic) {
+ AbstractValidationDiagnostic avd = (AbstractValidationDiagnostic) diagnostic;
+ if (avd.getSourceEObject() == targetObject && avd.getIssueCode().equals(issueCode)) {
+ return;
+ }
+ }
+ }
+ fail("Issue with code '" + issueCode + "' not found");
+ }
+
+ /**
+ * Assert that diagnosticList does not contain a diagnostic of the given issueCode.
+ *
+ * @param diagnostics
+ * the diagnostic to check for issues
+ * @param issueCode
+ * the code of the issue to look for
+ */
+ private void assertNoDiagnostic(final Diagnostic diagnostics, final String issueCode) {
+ for (Diagnostic diagnostic : diagnostics.getChildren()) {
+ if (((AbstractValidationDiagnostic) diagnostic).getIssueCode().equals(issueCode)) {
+ fail("Issue with code '" + issueCode + "' found");
+ return;
+ }
+ }
+ }
+
+ /**
+ * Assert that diagnosticList does not contain any diagnostic.
+ *
+ * @param diagnostics
+ * the diagnostic to check for issues
+ */
+ private void assertNoDiagnostics(final Diagnostic diagnostics) {
+ assertEquals(diagnostics.getCode(), Diagnostic.OK, "Diagnostics should be in OK state.");
+ assertTrue(diagnostics.getChildren().isEmpty(), "There should be no diagnostics. Instead found " + diagnostics.getChildren().size());
+ }
+
+ /**
+ * Assert no errors on resource exist.
+ *
+ * @param object
+ * the object
+ */
+ public static void assertNoErrorsOnResource(final EObject object) {
+ final EList errors = object.eResource().getErrors();
+ if (!errors.isEmpty()) {
+ fail(AbstractValidationTest.NO_ERRORS_FOUND_ON_RESOURCE_MESSAGE + "; found " + Lists.transform(errors, Resource.Diagnostic::getMessage)); //$NON-NLS-1$
+ }
+ }
+
+ /**
+ * Assert no errors on resource with the given message exist.
+ *
+ * @param object
+ * the object
+ * @param messages
+ * the messages
+ */
+ public static void assertNoErrorsOnResource(final EObject object, final String... messages) {
+ List messageList = Arrays.asList(messages);
+ final EList errors = object.eResource().getErrors();
+ for (String errorMessage : Lists.transform(errors, Resource.Diagnostic::getMessage)) {
+ assertFalse(messageList.contains(errorMessage), NO_ERRORS_FOUND_ON_RESOURCE_MESSAGE + " with message '" + errorMessage + "'.");
+ }
+ }
+
+ /**
+ * Assert no linking errors on resource with the given message exist.
+ *
+ * @param object
+ * the object
+ * @param referenceType
+ * the type of the referenced elements
+ * @param referenceNames
+ * the names of the referenced elements
+ */
+ public static void assertNoLinkingErrorsOnResource(final EObject object, final String referenceType, final String... referenceNames) {
+ final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
+ final List errorMessages = Lists.transform(linkingErrors, Resource.Diagnostic::getMessage);
+ for (final String referenceName : referenceNames) {
+ boolean found = false;
+ for (final String errMessage : errorMessages) {
+ if (errMessage.startsWith(referenceName)) {
+ found = true;
+ break;
+ }
+ }
+ assertFalse(found, NLS.bind("Expecting no linking errors on resource for \"{0}\".", referenceName));
+ }
+ }
+
+ /**
+ * Assert linking errors on resource with the given message exist.
+ *
+ * @param object
+ * the object
+ * @param referenceType
+ * the type of the referenced elements
+ * @param referenceNames
+ * the names of the referenced elements
+ */
+ public static void assertLinkingErrorsOnResourceExist(final EObject object, final String referenceType, final String... referenceNames) {
+ final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
+ final List errorMessages = Lists.transform(linkingErrors, Resource.Diagnostic::getMessage);
+ for (final String referenceName : referenceNames) {
+ boolean found = false;
+ for (final String errMessage : errorMessages) {
+ if (errMessage.contains(referenceName)) {
+ found = true;
+ break;
+ }
+ }
+ assertTrue(found, NLS.bind("Expected linking error on \"{0}\" but could not find it", referenceName));
+ }
+ }
+
+ /**
+ * Expect the given linking error messages on the resource of the given model.
+ *
+ * @param object
+ * the object, must not be {@code null}
+ * @param errorStrings
+ * the expected linking error error messages, must not be {@code null}
+ */
+ public static void assertLinkingErrorsWithCustomMessageOnResourceExist(final EObject object, final String... errorStrings) {
+ final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
+ final List errorMessages = Lists.transform(linkingErrors, Resource.Diagnostic::getMessage);
+ for (final String s : errorStrings) {
+ assertTrue(errorMessages.contains(s), NLS.bind("Expected linking error \"{0}\" but could not find it", s));
+ }
+ }
+
+ /**
+ * Assert no linking errors on resource with the given message exist.
+ *
+ * @param object
+ * the object, must not be {@code null}
+ * @param messages
+ * the linking error messages, must not be {@code null}
+ */
+ public static void assertNoLinkingErrorsWithCustomMessageOnResource(final EObject object, final String... messages) {
+ List messageList = Arrays.asList(messages);
+ final List linkingErrors = object.eResource().getErrors().stream().filter(error -> error instanceof XtextLinkingDiagnostic).collect(Collectors.toList());
+ for (String errorMessage : Lists.transform(linkingErrors, Resource.Diagnostic::getMessage)) {
+ assertFalse(messageList.contains(errorMessage), NLS.bind("Expecting no linking errors on resource with message \"{0}\".", errorMessage));
+ }
+ }
+
+ /**
+ * Expect given error messages on the resource of given model.
+ *
+ * @param object
+ * the object
+ * @param errorStrings
+ * the error strings
+ */
+ public static void assertErrorsOnResourceExist(final EObject object, final String... errorStrings) {
+ final EList errors = object.eResource().getErrors();
+ final List errorMessages = Lists.transform(errors, Resource.Diagnostic::getMessage);
+ for (final String s : errorStrings) {
+ assertTrue(errorMessages.contains(s), NLS.bind("Expected error \"{0}\" but could not find it", s));
+ }
+ }
+
+ /**
+ * Validates if there is a syntax error present in the source content.
+ *
+ * @param sourceFileName
+ * the file name that should be associated with the parsed content, must not be {@code null}
+ * @param sourceContent
+ * source, must not be {@code null}
+ */
+ protected void assertNoSyntaxErrorsOnResource(final String sourceFileName, final CharSequence sourceContent) {
+ final XtextTestSource testSource = createTestSource(sourceFileName, sourceContent.toString());
+ final List errors = testSource.getModel().eResource().getErrors().stream().filter(error -> error instanceof XtextSyntaxDiagnostic).collect(Collectors.toList());
+ if (!errors.isEmpty()) {
+ StringBuilder sb = new StringBuilder("Syntax error is present in the test source.\nList of all found syntax errors:");
+ errors.forEach(err -> sb.append("\n\t ").append(err.getMessage()));
+ throw new AssertionError(sb.toString());
+ }
+ }
+
+ /**
+ * Memorize the position and issue code of each resource error that appears in the file.
+ *
+ * @param root
+ * root node of the model to be analyzed
+ */
+ protected void memorizeUnexpectedResourceErrors() {
+ for (Resource.Diagnostic diagnostic : getUnexpectedResourceDiagnostics()) {
+ if (diagnostic instanceof AbstractDiagnostic) {
+ AbstractDiagnostic diag = (AbstractDiagnostic) diagnostic;
+ // Create error message
+ StringBuilder sb = new StringBuilder(35);
+ sb.append("Unexpected diagnostic found. Code '");
+ sb.append(diag.getCode());
+ sb.append(DOT_AND_LINEBREAK);
+ // Retrieve the position and add the error
+ memorizeErrorOnPosition(diag.getOffset(), sb.toString());
+ } else {
+ // Create error message
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Unexpected diagnostic found. '");
+ sb.append(diagnostic.toString());
+ sb.append(DOT_AND_LINEBREAK);
+ // Add error message
+ memorizeErrorOnPosition(0, sb.toString());
+ }
+ }
+ }
+
+ /**
+ * Memorize the position and issue code of each unexpected diagnostic that appears in the file.
+ * A diagnostic is considered as expected if a marker with the issue code in the test file was set.
+ */
+ protected void memorizeUnexpectedErrors() {
+ for (Diagnostic diagnostic : getUnexpectedDiagnostics()) {
+ if (diagnostic instanceof AbstractValidationDiagnostic) {
+ AbstractValidationDiagnostic avd = (AbstractValidationDiagnostic) diagnostic;
+ // Create error message
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Unexpected issue found. Code '");
+ sb.append(avd.getIssueCode());
+ sb.append(DOT_AND_LINEBREAK);
+ // Retrieve the position and add the error
+ if (avd instanceof FeatureBasedDiagnostic && ((FeatureBasedDiagnostic) avd).getFeature() != null) {
+ List nodes = NodeModelUtils.findNodesForFeature(avd.getSourceEObject(), ((FeatureBasedDiagnostic) avd).getFeature());
+ if (nodes.isEmpty()) {
+ INode node = NodeModelUtils.getNode(avd.getSourceEObject());
+ memorizeErrorOnPosition(getXtextTestUtil().findFirstNonHiddenLeafNode(node).getTotalOffset(), sb.toString());
+ } else {
+ for (INode node : nodes) {
+ memorizeErrorOnPosition(getXtextTestUtil().findFirstNonHiddenLeafNode(node).getTotalOffset(), sb.toString());
+ }
+ }
+ } else if (avd instanceof RangeBasedDiagnostic) {
+ memorizeErrorOnPosition(((RangeBasedDiagnostic) avd).getOffset(), sb.toString());
+ } else {
+ memorizeErrorOnPosition(NodeModelUtils.getNode(avd.getSourceEObject()).getTotalOffset(), sb.toString());
+ }
+ } else {
+ // Create error message
+ StringBuilder sb = new StringBuilder(30);
+ sb.append("Unexpected diagnostic found. '");
+ sb.append(diagnostic.toString());
+ sb.append(DOT_AND_LINEBREAK);
+ // Add error message
+ memorizeErrorOnPosition(0, sb.toString());
+ }
+ }
+ }
+
+ /**
+ * Strictly validates a source given by a file name and content.
+ *
+ * @param sourceFileName
+ * the file name that should be associated with the parsed content, must not be {@code null}
+ * @param sourceType
+ * defines if the source is a kernel or customer source, must not be {@code null}
+ * @param sourceContent
+ * source, must not be {@code null}
+ */
+ protected void validateStrictly(final String sourceFileName, final TestSourceType sourceType, final CharSequence sourceContent) {
+ XtextTestSource testSource = processMarkers(sourceFileName, sourceType, sourceContent);
+ memorizeUnexpectedErrors();
+ memorizeUnexpectedResourceErrors();
+ processErrorsFound(testSource.getContent());
+ afterValidate();
+ }
+
+ /**
+ * Strictly validate a kernel source given by a {@link Pair} of file name and content.
+ * All not expected diagnostics are considered as an error.
+ *
+ * @param sourceFileNameAndContent
+ * the file name and content, given as the key and value of the pair, respectively, must not be {@code null}
+ */
+ protected void validateKernelSourceStrictly(final Pair sourceFileNameAndContent) {
+ validateKernelSourceStrictly(sourceFileNameAndContent.getKey(), sourceFileNameAndContent.getValue());
+ }
+
+ /**
+ * Strictly validate a customer source given by a {@link Pair} of file name and content.
+ * All not expected diagnostics are considered as an error.
+ *
+ * @param sourceFileNameAndContent
+ * the file name and content, given as the key and value of the pair, respectively, must not be {@code null}
+ */
+ protected void validateCustomerSourceStrictly(final Pair sourceFileNameAndContent) {
+ validateCustomerSourceStrictly(sourceFileNameAndContent.getKey(), sourceFileNameAndContent.getValue());
+ }
+
+ /**
+ * Strictly validate a kernel source given by a file name and content.
+ * All not expected diagnostics are considered as an error.
+ *
+ * @param sourceFileName
+ * the file name that should be associated with the parsed content, must not be {@code null}
+ * @param sourceContent
+ * source, must not be {@code null}
+ */
+ protected void validateKernelSourceStrictly(final String sourceFileName, final CharSequence sourceContent) {
+ validateStrictly(sourceFileName, TestSourceType.CLIENT_ALL, sourceContent);
+ }
+
+ /**
+ * Strictly validate a customer source given by a file name and content.
+ * All not expected diagnostics are considered as an error.
+ *
+ * @param sourceFileName
+ * the file name that should be associated with the parsed content, must not be {@code null}
+ * @param sourceContent
+ * source, must not be {@code null}
+ */
+ protected void validateCustomerSourceStrictly(final String sourceFileName, final CharSequence sourceContent) {
+ validateStrictly(sourceFileName, TestSourceType.CLIENT_CUSTOMER, sourceContent);
+ }
+}
diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextEditorTest.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextEditorTest.java
index 0b443eb511..29a544bd67 100644
--- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextEditorTest.java
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextEditorTest.java
@@ -1,203 +1,203 @@
-/*******************************************************************************
- * 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 static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-
-import org.eclipse.emf.common.util.WrappedException;
-import org.eclipse.jface.text.BadLocationException;
-import org.eclipse.ui.IEditorPart;
-import org.eclipse.ui.PlatformUI;
-import org.eclipse.xtext.resource.XtextResource;
-import org.eclipse.xtext.ui.editor.XtextEditor;
-import org.eclipse.xtext.ui.editor.XtextSourceViewer;
-import org.eclipse.xtext.ui.editor.model.IXtextDocument;
-import org.eclipse.xtext.ui.editor.reconciler.XtextReconciler;
-import org.eclipse.xtext.util.concurrent.IUnitOfWork;
-
-import com.avaloq.tools.ddk.xtext.test.TestSource;
-import com.avaloq.tools.ddk.xtext.test.XtextTestSource;
-
-
-/**
- * AbstractXtextEditorTest provides convenient setup and access functionality for tests that require an xtext editor.
- */
-@SuppressWarnings("nls")
-public abstract class AbstractXtextEditorTest extends AbstractXtextMarkerBasedTest {
-
- private static final String EDITOR_MUST_NOT_BE_DIRTY = "Editor must not be dirty - this indicates state carried over";
- private static final String EDITOR_HAS_NO_DOCUMENT = "Editor has no document";
- private static final String EDITOR_COULD_NOT_BE_OPENED_WITH_URI = "Editor could not be opened with URI: ";
-
- protected static final String CR_LF = "\r\n";
- protected static final String LF = "\n";
-
- @Override
- protected void beforeAllTests() {
- super.beforeAllTests();
- // For Xtend-based tests there is no default test source associated with the test class
- TestSource testSource = getTestSource();
- if (testSource != null) {
- openEditor(testSource);
- }
- }
-
- @Override
- protected void afterAllTests() {
- closeOpenEditor();
- super.afterAllTests();
- }
-
- @Override
- protected void beforeApplyAssertions(final XtextTestSource testSource) {
- super.beforeApplyAssertions(testSource);
- openEditor(testSource);
- }
-
- @Override
- protected void afterValidate() {
- closeOpenEditor();
- super.afterValidate();
- }
-
- /**
- * Opens the editor with the given test source.
- *
- * @param testSource
- * the test source to open, not {@code null}
- */
- private void openEditor(final TestSource testSource) {
- // if openEditor returns NULL, then one possible cause might be that the Activator
- // has not been set correctly in the presentation plug-in MANIFEST of that grammar.
- XtextEditor editor = getXtextTestUtil().openEditor(testSource.getUri());
- assertNotNull(editor, EDITOR_COULD_NOT_BE_OPENED_WITH_URI + testSource.getUri());
- getTestInformation().putTestObject(XtextEditor.class, editor);
- assertNotNull(getDocument(), EDITOR_HAS_NO_DOCUMENT);
- assertFalse(getEditor().isDirty(), EDITOR_MUST_NOT_BE_DIRTY);
- }
-
- /**
- * Open editor of the test source with a given file name.
- *
- * @param fileName
- * file name of the source to open editor for, must not be {@code null}
- */
- protected void openEditor(final String fileName) {
- openEditor(getTestSource(fileName));
- }
-
- /**
- * Closes the currently open editor.
- */
- private void closeOpenEditor() {
- final XtextEditor editor = getEditor();
- if (editor != null) {
- closeEditor(editor, false);
- }
- }
-
- /**
- * Returns the editor.
- *
- * @return the editor
- */
- protected XtextEditor getEditor() {
- return (XtextEditor) getTestInformation().getTestObject(XtextEditor.class);
- }
-
- /**
- * Closes the given editor-part - contrary to {@link org.eclipse.ui.texteditor.AbstractTextEditor#close(boolean)} this call is blocking!
- *
- * @param editor
- * the editor to close
- * @param save
- * true if should save before close, false otherwise
- */
- protected void closeEditor(final IEditorPart editor, final boolean save) {
- Object editorJobs = getTestUtil().getEditorJobFamily(editor);
- PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
- @Override
- public void run() {
- PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeEditor(editor, save);
- }
- });
- if (editorJobs != null) {
- waitForJobsOfFamily(editorJobs);
- }
- }
-
- /**
- * Returns the viewer.
- *
- * @return the viewer
- */
- protected XtextSourceViewer getViewer() {
- return (XtextSourceViewer) getEditor().getInternalSourceViewer();
- }
-
- /**
- * Returns the document.
- *
- * @return the document
- */
- protected IXtextDocument getDocument() {
- return getEditor().getDocument();
- }
-
- /**
- * Insert the given value at offset in {@link #getDocument()}.
- *
- * @param offset
- * the offset
- * @param value
- * the value
- */
- protected void insertAtOffset(final int offset, final String value) {
- PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
- @Override
- public void run() {
- // the modify() is invoked to ensure a (fast-only) re-validation of the document is triggered
- getDocument().modify(new IUnitOfWork() {
- @Override
- public java.lang.Void exec(final XtextResource state) {
- try {
- getDocument().replace(offset, 0, value);
- } catch (BadLocationException e) {
- throw new WrappedException("Could not insert \"" + value + "\" at affset " + offset, e);
- }
- return null;
- }
- });
- }
- });
- }
-
- /**
- * Test that the editor does not allow "Save as...".
- *
- * @deprecated Provide this test method in an appropriate test class for the editor under test, if the editor shall not allow "Save as...".
- */
- @Deprecated
- public void testSaveAsDisallowed() {
- final XtextEditor editor = getEditor();
- assertFalse(editor.isSaveAsAllowed(), "Editor must not allow 'Save as...'");
- }
-
- @Override
- protected void waitForValidation() {
- // Editor tests frequently work by modifying the document. We first need to wait for the reconciler to run, otherwise we may
- // actually get results from before a document change is reflected in the document's resource, leading to spurious errors.
- // Note that the XtextReconciler runs with a delay of 500ms.
- waitForJobsOfFamily(XtextReconciler.class.getName());
- super.waitForValidation();
- }
-}
+/*******************************************************************************
+ * 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 static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import org.eclipse.emf.common.util.WrappedException;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.ui.editor.XtextEditor;
+import org.eclipse.xtext.ui.editor.XtextSourceViewer;
+import org.eclipse.xtext.ui.editor.model.IXtextDocument;
+import org.eclipse.xtext.ui.editor.reconciler.XtextReconciler;
+import org.eclipse.xtext.util.concurrent.IUnitOfWork;
+
+import com.avaloq.tools.ddk.xtext.test.TestSource;
+import com.avaloq.tools.ddk.xtext.test.XtextTestSource;
+
+
+/**
+ * AbstractXtextEditorTest provides convenient setup and access functionality for tests that require an xtext editor.
+ */
+@SuppressWarnings("nls")
+public abstract class AbstractXtextEditorTest extends AbstractXtextMarkerBasedTest {
+
+ private static final String EDITOR_MUST_NOT_BE_DIRTY = "Editor must not be dirty - this indicates state carried over";
+ private static final String EDITOR_HAS_NO_DOCUMENT = "Editor has no document";
+ private static final String EDITOR_COULD_NOT_BE_OPENED_WITH_URI = "Editor could not be opened with URI: ";
+
+ protected static final String CR_LF = "\r\n";
+ protected static final String LF = "\n";
+
+ @Override
+ protected void beforeAllTests() {
+ super.beforeAllTests();
+ // For Xtend-based tests there is no default test source associated with the test class
+ TestSource testSource = getTestSource();
+ if (testSource != null) {
+ openEditor(testSource);
+ }
+ }
+
+ @Override
+ protected void afterAllTests() {
+ closeOpenEditor();
+ super.afterAllTests();
+ }
+
+ @Override
+ protected void beforeApplyAssertions(final XtextTestSource testSource) {
+ super.beforeApplyAssertions(testSource);
+ openEditor(testSource);
+ }
+
+ @Override
+ protected void afterValidate() {
+ closeOpenEditor();
+ super.afterValidate();
+ }
+
+ /**
+ * Opens the editor with the given test source.
+ *
+ * @param testSource
+ * the test source to open, not {@code null}
+ */
+ private void openEditor(final TestSource testSource) {
+ // if openEditor returns NULL, then one possible cause might be that the Activator
+ // has not been set correctly in the presentation plug-in MANIFEST of that grammar.
+ XtextEditor editor = getXtextTestUtil().openEditor(testSource.getUri());
+ assertNotNull(editor, EDITOR_COULD_NOT_BE_OPENED_WITH_URI + testSource.getUri());
+ getTestInformation().putTestObject(XtextEditor.class, editor);
+ assertNotNull(getDocument(), EDITOR_HAS_NO_DOCUMENT);
+ assertFalse(getEditor().isDirty(), EDITOR_MUST_NOT_BE_DIRTY);
+ }
+
+ /**
+ * Open editor of the test source with a given file name.
+ *
+ * @param fileName
+ * file name of the source to open editor for, must not be {@code null}
+ */
+ protected void openEditor(final String fileName) {
+ openEditor(getTestSource(fileName));
+ }
+
+ /**
+ * Closes the currently open editor.
+ */
+ private void closeOpenEditor() {
+ final XtextEditor editor = getEditor();
+ if (editor != null) {
+ closeEditor(editor, false);
+ }
+ }
+
+ /**
+ * Returns the editor.
+ *
+ * @return the editor
+ */
+ protected XtextEditor getEditor() {
+ return (XtextEditor) getTestInformation().getTestObject(XtextEditor.class);
+ }
+
+ /**
+ * Closes the given editor-part - contrary to {@link org.eclipse.ui.texteditor.AbstractTextEditor#close(boolean)} this call is blocking!
+ *
+ * @param editor
+ * the editor to close
+ * @param save
+ * true if should save before close, false otherwise
+ */
+ protected void closeEditor(final IEditorPart editor, final boolean save) {
+ Object editorJobs = getTestUtil().getEditorJobFamily(editor);
+ PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeEditor(editor, save);
+ }
+ });
+ if (editorJobs != null) {
+ waitForJobsOfFamily(editorJobs);
+ }
+ }
+
+ /**
+ * Returns the viewer.
+ *
+ * @return the viewer
+ */
+ protected XtextSourceViewer getViewer() {
+ return (XtextSourceViewer) getEditor().getInternalSourceViewer();
+ }
+
+ /**
+ * Returns the document.
+ *
+ * @return the document
+ */
+ protected IXtextDocument getDocument() {
+ return getEditor().getDocument();
+ }
+
+ /**
+ * Insert the given value at offset in {@link #getDocument()}.
+ *
+ * @param offset
+ * the offset
+ * @param value
+ * the value
+ */
+ protected void insertAtOffset(final int offset, final String value) {
+ PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ // the modify() is invoked to ensure a (fast-only) re-validation of the document is triggered
+ getDocument().modify(new IUnitOfWork() {
+ @Override
+ public java.lang.Void exec(final XtextResource state) {
+ try {
+ getDocument().replace(offset, 0, value);
+ } catch (BadLocationException e) {
+ throw new WrappedException("Could not insert \"" + value + "\" at affset " + offset, e);
+ }
+ return null;
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Test that the editor does not allow "Save as...".
+ *
+ * @deprecated Provide this test method in an appropriate test class for the editor under test, if the editor shall not allow "Save as...".
+ */
+ @Deprecated
+ public void testSaveAsDisallowed() {
+ final XtextEditor editor = getEditor();
+ assertFalse(editor.isSaveAsAllowed(), "Editor must not allow 'Save as...'");
+ }
+
+ @Override
+ protected void waitForValidation() {
+ // Editor tests frequently work by modifying the document. We first need to wait for the reconciler to run, otherwise we may
+ // actually get results from before a document change is reflected in the document's resource, leading to spurious errors.
+ // Note that the XtextReconciler runs with a delay of 500ms.
+ waitForJobsOfFamily(XtextReconciler.class.getName());
+ super.waitForValidation();
+ }
+}
diff --git a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextMarkerBasedTest.java b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextMarkerBasedTest.java
index e4bccc5a69..38a5ab84c0 100644
--- a/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextMarkerBasedTest.java
+++ b/com.avaloq.tools.ddk.xtext.test.core/src/com/avaloq/tools/ddk/xtext/test/jupiter/AbstractXtextMarkerBasedTest.java
@@ -1,656 +1,656 @@
-/*******************************************************************************
- * 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 static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
-import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.SortedMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.xtext.CrossReference;
-import org.eclipse.xtext.nodemodel.INode;
-import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
-import org.eclipse.xtext.xbase.lib.Procedures;
-
-import com.avaloq.tools.ddk.xtext.test.TagCompilationParticipant;
-import com.avaloq.tools.ddk.xtext.test.XtextTestSource;
-import com.google.common.collect.LinkedHashMultimap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
-
-
-/**
- * Abstract class that supports Xtend based test implementations to use markers in the test sources.
- */
-@SuppressWarnings("nls")
-public abstract class AbstractXtextMarkerBasedTest extends AbstractXtextTest {
-
- private static final String INVALID_TEST_CONFIGURATION = "Invalid test configuration. Missing org.eclipse.xtend.lib in MANIFEST.MF in this plugin?"; //$NON-NLS-1$
- private static final String LINE_BREAK = "\n";
- private static final String MARKER_START_GUARD = "##";
- private static final String MARKER_END_GUARD = "#";
- private static final String SPLITTING_LINE = "-------------------------------------------------\n";
- protected static final Pattern PATTERN = Pattern.compile(MARKER_START_GUARD + "(\\d+)" + MARKER_END_GUARD);
-
- /** The tag id. */
- private int localMarkerIdCounter;
-
- private final Map assertions = Maps.newHashMap();
- private final SortedMap errorsOnPosition = Maps.newTreeMap();
- /** Used Tags to find Duplicates. */
- private final Set usedTags = Sets.newHashSet();
-
- /**
- * Indicates if a testing source is a kernel or customer source.
- */
- protected enum TestSourceType {
- CLIENT_ALL,
- CLIENT_CUSTOMER
- }
-
- // --------------------------------------------------------------------------
- // AbstractModelAssertion
- // --------------------------------------------------------------------------
-
- /**
- * Interface for testing assertions on a given source position.
- */
- protected abstract class AbstractModelAssertion implements Procedures.Procedure2 {
-
- @Override
- public abstract void apply(EObject semanticModel, Integer pos);
-
- }
-
- // --------------------------------------------------------------------------
- // Methods of testing framework
- // --------------------------------------------------------------------------
-
- @Override
- protected void afterEachTest() {
- getMarkerTagsInfo().clearTags(localMarkerIdCounter);
- super.afterEachTest();
- assertions.clear();
- usedTags.clear();
- errorsOnPosition.clear();
- }
-
- @Override
- protected void beforeEachTest() {
- localMarkerIdCounter = 0;
- super.beforeEachTest();
- assertFalse(getMarkerTagsInfo().isInvalidTestClass(), INVALID_TEST_CONFIGURATION);
- }
-
- // --------------------------------------------------------------------------
- // Methods to be used by the actual testing classes
- // --------------------------------------------------------------------------
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void addKernelSourceToWorkspace(final String sourceFileName, final CharSequence sourceContent) {
- String processedContent = processContentAndRegisterOffsets(sourceFileName, sourceContent);
- super.addKernelSourceToWorkspace(sourceFileName, processedContent);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected void addCustomerSourceToWorkspace(final String sourceFileName, final CharSequence sourceContent) {
- String processedContent = processContentAndRegisterOffsets(CUSTOMER_SOURCE_PREFIX + sourceFileName, sourceContent);
- super.addCustomerSourceToWorkspace(sourceFileName, processedContent);
- }
-
- /**
- * Removes tags and stores them in the test info object.
- *
- * @param sourceFileName
- * Source file name, must not be {@code null}
- * @param sourceContent
- * Content of the test source (may contain tags), must not be {@code null}
- * @return Content without tags, never {@code null}
- */
- private String processContentAndRegisterOffsets(final String sourceFileName, final CharSequence sourceContent) {
- Map offsets = Maps.newHashMap();
- String content = removeMarkersFromContent(sourceContent, offsets);
- for (Entry tag : offsets.entrySet()) {
- getMarkerTagsInfo().registerRequiredSourceTag(tag.getKey(), sourceFileName, tag.getValue());
- }
- return content;
- }
-
- /**
- * Removes the Xtend markers from a source.
- *
- * @param sourceContent
- * the source content, not {@code null}
- * @param tagToOffset
- * Map to be populated with tag to offset pairs, must not be {@code null}
- * @return the content without markers, never {@code null}
- */
- private String removeMarkersFromContent(final CharSequence sourceContent, final Map tagToOffset) {
- StringBuffer withoutMarkers = new StringBuffer(sourceContent.length());
- Matcher m = PATTERN.matcher(sourceContent);
- while (m.find()) {
- m.appendReplacement(withoutMarkers, "");
- tagToOffset.put(Integer.parseInt(m.group(1)), withoutMarkers.length());
- }
- m.appendTail(withoutMarkers);
- return withoutMarkers.toString();
- }
-
- /**
- * Returns the model for the given source name and string.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, must not be {@code null}
- * @param sourceContent
- * source, must not be {@code null}
- * @return Model element for the parsed source, may be {@code null}
- */
- protected EObject getModel(final String sourceFileName, final CharSequence sourceContent) {
- Map offsets = Maps.newHashMap();
- String content = removeMarkersFromContent(sourceContent, offsets);
- EObject root = null;
- try {
- root = getXtextTestUtil().getModel(sourceFileName, content);
- INode node = NodeModelUtils.getNode(root);
- for (Entry tag : offsets.entrySet()) {
- INode leafNode = NodeModelUtils.findLeafNodeAtOffset(node, tag.getValue() + 1);
- EObject context = NodeModelUtils.findActualSemanticObjectFor(leafNode);
- // Search for cross reference
- CrossReference crossReference = null;
- while (leafNode != null) {
- if (leafNode.getGrammarElement() instanceof CrossReference) {
- crossReference = (CrossReference) leafNode.getGrammarElement();
- break;
- }
- leafNode = leafNode.getParent();
- }
- getMarkerTagsInfo().registerLocalTag(tag.getKey(), context, crossReference);
- }
- } catch (IOException e) {
- fail("Exception while creating model from input string: " + e.getMessage()); //$NON-NLS-1$
- }
- return root;
- }
-
- /**
- * Does the same as get model, but returns void.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content
- * @param sourceContent
- * source
- */
- protected void registerModel(final String sourceFileName, final CharSequence sourceContent) {
- getModel(sourceFileName, sourceContent);
- }
-
- /**
- * Validate a kernel source given by a file name and content.
- * All not expected diagnostics are ignored.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, not {@code null}
- * @param sourceContent
- * source, not {@code null}
- */
- protected void validateKernelSource(final String sourceFileName, final CharSequence sourceContent) {
- validate(sourceFileName, TestSourceType.CLIENT_ALL, sourceContent);
- }
-
- /**
- * Validate a customer source given by a file name and content.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, not {@code null}
- * @param sourceContent
- * source, not {@code null}
- */
- protected void validateCustomerSource(final String sourceFileName, final CharSequence sourceContent) {
- validate(sourceFileName, TestSourceType.CLIENT_CUSTOMER, sourceContent);
- }
-
- // --------------------------------------------------------------------------
- // Methods to be used by more specific abstract classes
- // --------------------------------------------------------------------------
-
- /**
- * Add assertion to the list of assertions and return the corresponding string marker.
- *
- * @param assertion
- * assertion to be added, not {@code null}
- * @return
- * string marker corresponding to the added assertion
- */
- protected String addAssertion(final AbstractModelAssertion assertion) {
- // Assertions are given a local tag as they are local markers affecting and declared in only one source
- Integer markerId = getTag();
- assertions.put(markerId, assertion);
- return mark(markerId);
- }
-
- /**
- * Creates a mark with the given id. Use this method in tests to insert marks.
- *
- * If the given mark id is 0 then it means that the test was not well configured. All the id-s that are generated by {@link #getTag} start with 1, all the
- * global ids start with {@value com.avaloq.tools.asmd.testbase.scoping.TagCompilationParticipant#COUNTER_BASE}. Since it is unlikely to get local ids wrong,
- * the most common reason for global ids to be wrong is that they were not initialized. Global ids get initialized with active annotation
- * {@link com.avaloq.tools.asmd.testbase.scoping.Tag} that executes {@link com.avaloq.tools.asmd.testbase.scoping.TagCompilationParticipant}. Active
- * annotations do not get executed if {@code org.eclipse.xtend.lib} is missing in {@code MANIFEST.MF}. Thus we report this common mistake to the user via an
- * assertion.
- *
- *
- * @param id
- * Mark id
- * @return Mark text to be inserted in the source file, never {@code null}
- */
- protected String mark(final int id) {
- assertFalse(usedTags.contains(id), "Tag with " + id + " used to mark more than one location."); //$NON-NLS-1$ //$NON-NLS-2$
- usedTags.add(id);
- if (id < 1) {
- getMarkerTagsInfo().setTestClassInvalid();
- throw new AssertionError(INVALID_TEST_CONFIGURATION);
- }
- return MARKER_START_GUARD + id + MARKER_END_GUARD;
- }
-
- /**
- * Memorize an error that was detected during the validation of the current testing file.
- *
- * @param position
- * position of the error in file, not {@code null}
- * @param error
- * string with error, not {@code null}
- */
- protected void memorizeErrorOnPosition(final Integer position, final String error) {
- if (!errorsOnPosition.containsKey(position)) {
- errorsOnPosition.put(position, new StringBuilder());
- }
- errorsOnPosition.get(position).append(error);
- }
-
- /**
- * Processes all the inserted markers in a given source and creates a new {@link XtextTestSource} without the markers.
- *
- * @param sourceFileName
- * the name of the source, may be {@code null}
- * @param sourceType
- * the type of the source, may be {@code null}
- * @param sourceContent
- * the content of the source, must not be {@code null}
- * @return the {@link XtextTestSource} created.
- */
- protected XtextTestSource processMarkers(final String sourceFileName, final TestSourceType sourceType, final CharSequence sourceContent) {
- StringBuilder withoutMarkers = new StringBuilder();
- final Multimap positionToAssertionMap = LinkedHashMultimap.create();
- Matcher m = PATTERN.matcher(sourceContent);
- int lastEnd = 0;
- while (m.find()) {
- withoutMarkers.append(sourceContent.subSequence(lastEnd, m.start()));
- lastEnd = m.end();
- int markerId = Integer.parseInt(m.group(1));
- // save the position of the marker only if we are dealing with an assertion marker
- AbstractModelAssertion assertionMarker = assertions.get(markerId);
- if (assertionMarker != null) {
- positionToAssertionMap.put(withoutMarkers.length(), assertionMarker);
- }
- }
- // Add the rest part of input string
- withoutMarkers.append(sourceContent.subSequence(lastEnd, sourceContent.length()));
-
- // Calculate source name
- String fullSourceFileName = "";
- if (sourceType == TestSourceType.CLIENT_CUSTOMER) {
- fullSourceFileName = CUSTOMER_SOURCE_PREFIX;
- }
- fullSourceFileName = fullSourceFileName.concat(sourceFileName);
-
- EObject semanticModel;
- String withoutMarkersAsString = withoutMarkers.toString();
- XtextTestSource testSource = createTestSource(fullSourceFileName, withoutMarkersAsString);
- semanticModel = testSource.getModel();
- beforeApplyAssertions(testSource);
- // Run validations on markers
- for (Map.Entry entry : positionToAssertionMap.entries()) {
- entry.getValue().apply(semanticModel, entry.getKey());
- }
- return testSource;
- }
-
- /**
- * Validate a source given by a file name and content.
- *
- * @param sourceFileName
- * the file name that should be associated with the parsed content, not {@code null}
- * @param sourceType
- * defines if the source is a kernel or customer source, not {@code null}
- * @param sourceContent
- * source, not {@code null}
- */
- protected void validate(final String sourceFileName, final TestSourceType sourceType, final CharSequence sourceContent) {
- XtextTestSource testSource = processMarkers(sourceFileName, sourceType, sourceContent);
- processErrorsFound(testSource.getContent());
- afterValidate();
- }
-
- /**
- * Processes all the diagnostics in a given source.
- *
- * @param sourceWithoutMarkers
- * the source to process, must not be {@code null}
- */
- protected void processErrorsFound(final String sourceWithoutMarkers) {
- if (!errorsOnPosition.isEmpty()) {
- // CHECKSTYLE:OFF MagicNumber
- StringBuilder sb = new StringBuilder(50);
- // CHECKSTYLE:ON
- sb.append(memorizedErrorsToString(sourceWithoutMarkers));
- sb.append(SPLITTING_LINE);
- sb.append("List of all found diagnostics:\n");
- sb.append(getAdditionalErrorMessageInformation());
- assertEquals(sourceWithoutMarkers, sb.toString(), "Errors found. Consider compare view.");
- }
- }
-
- /**
- * Inject memorized errors into the input file on positions where they were detected.
- *
- * @param source
- * text of the input testing source, not {@code null}
- * @return
- * input testing source with injected errors, never {@code null}
- */
- private String memorizedErrorsToString(final String source) {
- StringBuilder result = new StringBuilder();
- StringBuilder errorBuffer = new StringBuilder();
- // Sort positions
- List