Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/*******************************************************************************
* Copyright (c) 2016 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.check.core.test;

import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;

import org.eclipse.xtext.testing.InjectWith;
import org.eclipse.xtext.testing.extensions.InjectionExtension;
import org.eclipse.xtext.xbase.testing.JavaSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import com.avaloq.tools.ddk.check.CheckInjectorProvider;


/**
* Unit tests for the code generated for the various forms of the {@code issue} expression: markers on a text region, markers on a dynamically
* computed structural feature, and checks declared {@code external}.
*/
@InjectWith(CheckInjectorProvider.class)
@ExtendWith(InjectionExtension.class)
@SuppressWarnings("nls")
public class IssueExpressionGenerationTest extends AbstractCheckGenerationTestCase {

private static final String PACKAGE_NAME = "mypackage";

private static final String CATALOG_NAME = "MyCatalog";

/**
* An {@code issue ... at <region>} expression must produce an offset and length based marker, and must fall back to the object based marker for an
* absent or empty region.
*/
@Test
public void testRegionBasedIssue() {
final String source = """
package %s

import com.avaloq.tools.ddk.check.check.Documented
import org.eclipse.xtext.nodemodel.util.NodeModelUtils

catalog %s
for grammar com.avaloq.tools.ddk.check.Check {

live error ID1 "Label 1"
message "Message 1" {
for Documented elem {
issue on elem at NodeModelUtils.getNode(elem)
}
}
}
""".formatted(PACKAGE_NAME, CATALOG_NAME);

final String validator = generateAndRead(source, VALIDATOR_NAME_SUFFIX);
assertTrue(validator.contains(".getOffset()"), "The region offset should be passed to the acceptor");
assertTrue(validator.contains(".getLength()"), "The region length should be passed to the acceptor");
assertTrue(validator.contains("INSIGNIFICANT_INDEX"), "The fall back to an object based marker should be generated");
}

/**
* An {@code issue on <object> # (<expression>)} expression must pass the computed structural feature to the acceptor.
*/
@Test
public void testDynamicMarkerFeature() {
final String source = """
package %s

import com.avaloq.tools.ddk.check.check.CheckPackage
import com.avaloq.tools.ddk.check.check.Documented

catalog %s
for grammar com.avaloq.tools.ddk.check.Check {

live error ID1 "Label 1"
message "Message 1" {
for Documented elem {
issue on elem # (CheckPackage.eINSTANCE.checkCatalog_Name)
}
}
}
""".formatted(PACKAGE_NAME, CATALOG_NAME);

final String validator = generateAndRead(source, VALIDATOR_NAME_SUFFIX);
assertTrue(validator.replaceAll("\\s+", "").contains("getCheckCatalog_Name()"), "The computed structural feature should be passed to the acceptor");
}

/**
* An {@code external} check carries no issue expression, yet its issue code and label must still be generated for the hand-written code that
* raises the issue, and its constraint must still be executed. The latter matters for checks that exist only to trigger the computation of a
* derived property.
*/
@Test
public void testExternalCheckProducesIssueCodeAndLabel() {
final String source = """
package %s

import com.avaloq.tools.ddk.check.check.Documented

catalog %s
for grammar com.avaloq.tools.ddk.check.Check {

external live error ID1 "Label 1"
message "Message 1" {
for Documented elem {
elem.description
}
}
}
""".formatted(PACKAGE_NAME, CATALOG_NAME);

final String issueCodes = generateAndRead(source, ISSUE_CODES_SUFFIX);
assertTrue(issueCodes.contains("ID_1"), "The issue code of an external check should be generated");

final String catalog = generateAndRead(source, CATALOG_NAME_SUFFIX);
assertTrue(catalog.replaceAll("\\s+", "").contains("put(MyCatalogIssueCodes.ID_1,\"Label1\")"), "The label of an external check should be generated");

final String validator = generateAndRead(source, VALIDATOR_NAME_SUFFIX);
assertTrue(validator.contains("getDescription()"), "The constraint of an external check should still be executed");
}

/**
* Generates the given catalog and returns the source of one of the generated Java classes.
*
* @param source
* the check catalog source, must not be {@code null}
* @param classNameSuffix
* the suffix identifying the generated class, must not be {@code null}
* @return the generated Java source, never {@code null}
*/
private String generateAndRead(final String source, final String classNameSuffix) {
final ByteArrayInputStream sourceStream = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8));
final List<JavaSource> compiledClassesList = generateAndCompile(sourceStream);
return compiledClassesList.stream() //
.filter(s -> s.getFileName().equals(CATALOG_NAME + classNameSuffix)) //
.findFirst() //
.orElseThrow() //
.getCode();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
@SuppressWarnings("nls")
public class CheckModelUtil {

private static final String ID = "ID";

/* Returns a base model stub with package (com.test), catalog (c) and grammar (g). */
public String modelWithGrammar() {
return """
Expand Down Expand Up @@ -66,7 +68,19 @@ public String modelWithCheck(final String id) {
* and message (MyMessage).
*/
public String modelWithCheck() {
return modelWithCheck("ID");
return modelWithCheck(ID);
}

/* Returns a base model stub with an external check of given ID. */
public String modelWithExternalCheck(final String id) {
return modelWithCategory() + """
external error %s "Some Error" ()
message "My Message" {""".formatted(id);
}

/* Returns a base model stub with an external check (ID). */
public String modelWithExternalCheck() {
return modelWithExternalCheck(ID);
}

/* Returns a dummy check with given ID. */
Expand All @@ -85,6 +99,14 @@ public String modelWithContext() {
return modelWithCheck() + "for ContextType ctx {";
}

/*
* Returns a base model stub with an external check and a context using context
* type ContextType 'ctx'.
*/
public String modelWithExternalContext() {
return modelWithExternalCheck() + "for ContextType ctx {";
}

/* Returns a base model stub with a give collection of contexts. */
public String modelWithContexts(final List<String> contexts) {
final StringBuilder builder = new StringBuilder(512);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.avaloq.tools.ddk.check.core.test.BugDsl27;
import com.avaloq.tools.ddk.check.core.test.CheckScopingTest;
import com.avaloq.tools.ddk.check.core.test.IssueCodeToLabelMapGenerationTest;
import com.avaloq.tools.ddk.check.core.test.IssueExpressionGenerationTest;
import com.avaloq.tools.ddk.check.core.test.ProjectBasedTests;
import com.avaloq.tools.ddk.check.formatting.CheckFormattingTest;
import com.avaloq.tools.ddk.check.validation.CheckApiAccessValidationsTest;
Expand All @@ -40,6 +41,7 @@
CheckValidationTest.class,
CheckJavaValidatorUtilTest.class,
IssueCodeToLabelMapGenerationTest.class,
IssueExpressionGenerationTest.class,
ProjectBasedTests.class,
BugAig1314.class,
BugDsl27.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.google.common.collect.Lists;
import com.google.inject.Inject;


/*
* Tests for various check validations as implemented in the validation classes
* <ul>
Expand Down Expand Up @@ -158,6 +159,7 @@ public void testGuardsPrecedeIssues() throws Exception {
}

/* Tests org.eclipse.xtext.xbase.validation.EarlyExitValidator.checkDeadCode(XBlockExpression) */
@SuppressWarnings("restriction")
@Test
public void testDeadCode() throws Exception {
// should not fail
Expand Down Expand Up @@ -192,6 +194,45 @@ public void testIssueExpressionExists() throws Exception {
helper.assertNoError(model, IssueCodes.MISSING_ISSUE_EXPRESSION);
}

/* Tests checkIssueExpressionExists(Context) for external checks, whose issues are raised by hand-written code. */
@Test
public void testIssueExpressionNotRequiredForExternalCheck() throws Exception {
final CheckCatalog model = parser.parse(modelUtil.modelWithExternalContext() + "null");
helper.assertNoError(model, IssueCodes.MISSING_ISSUE_EXPRESSION);
}

/* Tests checkExternalCheckHasNoIssue(Check) */
@Test
public void testIssueInExternalCheck() throws Exception {
// should not fail
CheckCatalog model = parser.parse(modelUtil.modelWithExternalContext() + "null");
helper.assertNoIssue(model, CheckPackage.Literals.CHECK, IssueCodes.ISSUE_IN_EXTERNAL_CHECK);

// should fail
model = parser.parse(modelUtil.modelWithExternalContext() + "issue");
helper.assertWarning(model, CheckPackage.Literals.CHECK, IssueCodes.ISSUE_IN_EXTERNAL_CHECK);
}

/* Tests checkMarkerRegionType(XIssueExpression) */
@Test
public void testMarkerRegionType() throws Exception {
final CheckCatalog model = parser.parse(modelUtil.modelWithContext() + "issue at 1");
helper.assertError(model, CheckPackage.Literals.XISSUE_EXPRESSION, IssueCodes.MARKER_REGION_TYPE);
}

/* Tests checkMarkerIndexNotCombinedWithRegion(XIssueExpression) */
@Test
public void testMarkerIndexNotCombinedWithRegion() throws Exception {
// The marker feature is required to terminate the marker object expression; without it Xbase parses "[0]" as a trailing closure.
// should not fail
CheckCatalog model = parser.parse(modelUtil.modelWithContext() + "issue on ctx#name [0]");
helper.assertNoError(model, IssueCodes.MARKER_INDEX_WITH_REGION);

// should fail
model = parser.parse(modelUtil.modelWithContext() + "issue on ctx#name [0] at 1");
helper.assertError(model, CheckPackage.Literals.XISSUE_EXPRESSION, IssueCodes.MARKER_INDEX_WITH_REGION);
}

/* Test checkCheckName(Check). ID is missing. */
@Test
public void testCheckIDIsMissing() throws Exception {
Expand Down Expand Up @@ -274,71 +315,61 @@ public void testCategoryLabelsAreNotUniqueOnceConverted() throws Exception {
/* Tests checkSeverityRangeOrder(Check) */
@Test
public void testSeverityRangeOrder_1() throws Exception {
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(WARNING, ERROR)),
IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(WARNING, ERROR)), IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
}

/* Tests checkSeverityRangeOrder(Check) */
@Test
public void testSeverityRangeOrder_2() throws Exception {
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(IGNORE, WARNING)),
IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(IGNORE, WARNING)), IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
}

/* Tests checkSeverityRangeOrder(Check) */
@Test
public void testSeverityRangeOrder_3() throws Exception {
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(INFO, INFO)),
IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(INFO, INFO)), IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
}

/* Tests checkSeverityRangeOrder(Check) */
@Test
public void testSeverityRangeOrder_4() throws Exception {
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(INFO, IGNORE)),
CheckPackage.Literals.SEVERITY_RANGE, IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(INFO, IGNORE)), CheckPackage.Literals.SEVERITY_RANGE, IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
}

/* Tests checkSeverityRangeOrder(Check) */
@Test
public void testSeverityRangeOrder_5() throws Exception {
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, INFO)),
CheckPackage.Literals.SEVERITY_RANGE, IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, INFO)), CheckPackage.Literals.SEVERITY_RANGE, IssueCodes.ILLEGAL_SEVERITY_RANGE_ORDER);
}

/* Tests checkDefaultSeverityInRange(Check) */
@Test
public void testDefaultSeverityInRange_1() throws Exception {
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(WARNING, ERROR, INFO)),
CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(WARNING, ERROR, INFO)), CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
}

/* Tests checkDefaultSeverityInRange(Check) */
@Test
public void testDefaultSeverityInRange_2() throws Exception {
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, INFO, IGNORE)),
CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, INFO, IGNORE)), CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
}

/* Tests checkDefaultSeverityInRange(Check) */
@Test
public void testDefaultSeverityInRange_3() throws Exception {
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, ERROR, IGNORE)),
CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
helper.assertError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, ERROR, IGNORE)), CheckPackage.Literals.CHECK, IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
}

/* Tests checkDefaultSeverityInRange(Check) */
@Test
public void testDefaultSeverityInRange_4() throws Exception {
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, ERROR, ERROR)),
IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(ERROR, ERROR, ERROR)), IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
}

/* Tests checkDefaultSeverityInRange(Check) */
@Test
public void testDefaultSeverityInRange_5() throws Exception {
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(INFO, ERROR, WARNING)),
IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
helper.assertNoError(parser.parse(modelUtil.modelWithSeverityRange(INFO, ERROR, WARNING)), IssueCodes.DEFAULT_SEVERITY_NOT_IN_RANGE);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<eStructuralFeatures xsi:type="ecore:EReference" name="severityRange" eType="#//SeverityRange"
containment="true"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="final" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="external" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EBoolean"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="kind" eType="#//TriggerKind"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="defaultSeverity" eType="#//SeverityKind"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="id" eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
Expand Down Expand Up @@ -104,10 +105,14 @@
<eClassifiers xsi:type="ecore:EClass" name="XIssueExpression" eSuperTypes="platform:/plugin/org.eclipse.xtext.xbase/model/Xbase.ecore#//XExpression">
<eStructuralFeatures xsi:type="ecore:EReference" name="check" eType="#//Check"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="markerFeature" eType="ecore:EClass platform:/plugin/org.eclipse.emf.ecore/model/Ecore.ecore#//EStructuralFeature"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="markerFeatureExpression" eType="ecore:EClass platform:/plugin/org.eclipse.xtext.xbase/model/Xbase.ecore#//XExpression"
containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="markerObject" eType="ecore:EClass platform:/plugin/org.eclipse.xtext.xbase/model/Xbase.ecore#//XExpression"
containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="markerIndex" eType="ecore:EClass platform:/plugin/org.eclipse.xtext.xbase/model/Xbase.ecore#//XExpression"
containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="markerRegion" eType="ecore:EClass platform:/plugin/org.eclipse.xtext.xbase/model/Xbase.ecore#//XExpression"
containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="message" eType="ecore:EClass platform:/plugin/org.eclipse.xtext.xbase/model/Xbase.ecore#//XExpression"
containment="true"/>
<eStructuralFeatures xsi:type="ecore:EReference" name="messageParameters" upperBound="-1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<genClasses ecoreClass="Check.ecore#//Check">
<genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Check.ecore#//Check/severityRange"/>
<genFeatures createChild="false" ecoreFeature="ecore:EAttribute Check.ecore#//Check/final"/>
<genFeatures createChild="false" ecoreFeature="ecore:EAttribute Check.ecore#//Check/external"/>
<genFeatures createChild="false" ecoreFeature="ecore:EAttribute Check.ecore#//Check/kind"/>
<genFeatures createChild="false" ecoreFeature="ecore:EAttribute Check.ecore#//Check/defaultSeverity"/>
<genFeatures createChild="false" ecoreFeature="ecore:EAttribute Check.ecore#//Check/id"/>
Expand Down Expand Up @@ -89,8 +90,10 @@
<genClasses ecoreClass="Check.ecore#//XIssueExpression">
<genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/check"/>
<genFeatures notify="false" createChild="false" propertySortChoices="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/markerFeature"/>
<genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/markerFeatureExpression"/>
<genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/markerObject"/>
<genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/markerIndex"/>
<genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/markerRegion"/>
<genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/message"/>
<genFeatures property="None" children="true" createChild="true" ecoreFeature="ecore:EReference Check.ecore#//XIssueExpression/messageParameters"/>
<genFeatures createChild="false" ecoreFeature="ecore:EAttribute Check.ecore#//XIssueExpression/issueCode"/>
Expand Down
Binary file not shown.
Loading