Skip to content

Commit

Permalink
Merge 5e0d9aa into e01aa63
Browse files Browse the repository at this point in the history
  • Loading branch information
morpfl authored Dec 12, 2024
2 parents e01aa63 + 5e0d9aa commit 36e9a53
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import com.buschmais.jqassistant.core.report.api.model.Column;
import com.buschmais.jqassistant.core.report.api.model.Result;
import com.buschmais.jqassistant.core.rule.api.model.*;
import com.buschmais.jqassistant.core.shared.xml.JAXBUnmarshaller;
import com.buschmais.jqassistant.core.shared.xml.JAXBHelper;
import com.buschmais.jqassistant.plugin.junit.impl.schema.Error;
import com.buschmais.jqassistant.plugin.junit.impl.schema.Failure;
import com.buschmais.jqassistant.plugin.junit.impl.schema.Testcase;
Expand All @@ -34,7 +34,7 @@ public class JUnitReportPluginTest extends AbstractReportPluginTest {

private static final String EXPECTED_CONTENT = "c = foo\n" + "---\n" + "c = bar\n";

private JAXBUnmarshaller<Testsuite> unmarshaller = new JAXBUnmarshaller(Testsuite.class);
private JAXBHelper<Testsuite> unmarshaller = new JAXBHelper(Testsuite.class);

private Group testGroup = Group.builder().id("test:Group").description("testGroup").build();
private Concept concept = Concept.builder().id("test:Concept").description("testConcept").severity(Severity.MINOR).build();
Expand Down
80 changes: 72 additions & 8 deletions plugin/java/src/main/resources/META-INF/jqassistant-rules/java.xml
Original file line number Diff line number Diff line change
Expand Up @@ -605,24 +605,88 @@
]]></cypher>
</concept>

<constraint id="java:TestMethodWithoutAssertion">
<concept id="java:AssertAnnotation">
<description>
An assert annotation is used to define an expected testing result (e.g. @Test(expected = RuntimeException.class)
for a test method.
</description>
<cypher><![CDATA[
MATCH
(type:Java:Type)-[:DECLARES]->(method:Method)-[:ANNOTATED_BY]->(annotation:Annotation:Assert)-[:OF_TYPE]->(annotationType:Java:Type)
RETURN
type AS DeclaringType, method AS AnnotatedTestMethod, annotationType AS AnnotationType
ORDER BY
type.fqn, method.fqn, annotationType.fqn
]]></cypher>
</concept>

<concept id="java:MethodPerformsAssertion">
<description>Returns all test methods performing at least one assertion.</description>
<cypher><![CDATA[
MATCH
(type:Java:Type)-[:DECLARES]->(method:Method)-[:PERFORMS_ASSERTION]->(assertion:Assert)
RETURN
type AS DeclaringType, method AS Method
ORDER BY
type.fqn, method.signature
]]></cypher>
</concept>

<concept id="java:TestMethodPerformsMethodAssertion">
<providesConcept refId="java:MethodPerformsAssertion"/>
<requiresConcept refId="java:VirtualInvokes"/>
<requiresConcept refId="java:TestMethod"/>
<requiresConcept refId="java:AssertMethod"/>
<requiresParameter name="javaTestAssertMaxCallDepth" type="int" defaultValue="3" />
<description>All test methods must perform assertions (within a call hierarchy of max. 3 steps).</description>
<description>
A test method performs a method assertion, if it invokes an assert method within a call hierarchy of max. 3 steps.
</description>
<cypher><![CDATA[
MATCH
(testClass:Java:Type)-[:DECLARES]->(testMethod:Test:Method)
OPTIONAL MATCH
path=shortestPath((testMethod)-[:INVOKES|VIRTUAL_INVOKES*]->(assert:Method:Assert))
path=shortestPath((testMethod)-[:INVOKES|VIRTUAL_INVOKES*]->(assertMethod:Method:Assert))
WITH
testClass, testMethod, COLLECT(length(path)) as lengths
WITH
testClass, testMethod, REDUCE(minVal = lengths[0], n IN lengths | CASE WHEN n < minVal THEN n ELSE minVal END) AS shortestPathLength
testClass, testMethod, assertMethod, length(path) as callDepth
WHERE
shortestPathLength is null
or shortestPathLength > $javaTestAssertMaxCallDepth
callDepth is not null
and callDepth <= $javaTestAssertMaxCallDepth
MERGE
(testMethod)-[:PERFORMS_ASSERTION]->(assertMethod)
RETURN
distinct testClass AS TestClass, testMethod AS TestMethod, assertMethod as AssertMethod
ORDER BY
testClass.fqn, testMethod.name, AssertMethod.name
]]></cypher>
</concept>

<concept id="java:TestMethodPerformsAnnotationAssertion">
<providesConcept refId="java:MethodPerformsAssertion"/>
<requiresConcept refId="java:TestMethod"/>
<requiresConcept refId="java:AssertAnnotation"/>
<description>
A test method performs an annotation assertion, if it is annotated with an assert annotation.
</description>
<cypher><![CDATA[
MATCH
(testClass:Java:Type)-[:DECLARES]->(testMethod:Test:Method)-[:ANNOTATED_BY]->(assertAnnotation:Annotation:Assert)-[:OF_TYPE]->(annotationType:Java:Type)
MERGE
(testMethod)-[:PERFORMS_ASSERTION]->(assertAnnotation)
RETURN
distinct testClass AS TestClass, testMethod as TestMethod, annotationType AS AssertAnnotationType
ORDER BY
testClass.fqn, testMethod.fqn
]]></cypher>
</concept>

<constraint id="java:TestMethodWithoutAssertion">
<requiresConcept refId="java:MethodPerformsAssertion"/>
<description>All test methods must perform at least one assertion.</description>
<cypher><![CDATA[
MATCH
(testClass:Java:Type)-[:DECLARES]->(testMethod:Test:Method)
WHERE NOT
(testMethod)-[:PERFORMS_ASSERTION]->(:Assert)
RETURN
distinct testClass AS TestClass, testMethod AS TestMethod
ORDER BY
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class JavaTestIT extends AbstractJavaPluginIT {
void setUp() {
query(
"MERGE (:Artifact)-[:CONTAINS]->(:Java:ByteCode:Type:Class{fqn:'Test'})-[:DECLARES]->(:Java:ByteCode:Member:Method:Test{signature:'void test()'})-[:INVOKES]->(:Java:ByteCode:Member:Method)-[:INVOKES]->(:Java:ByteCode:Member:Method:Assert)<-[:DECLARES]-(:Java:ByteCode:Type{fqn:'Assertions'})");
query(
"MERGE (testType:Java:ByteCode:Type:Class{fqn:'Test'})-[:DECLARES]->(testMethod:Java:ByteCode:Member:Method:Test{signature:'void annotatedTest()'})-[:ANNOTATED_BY]->(:Java:ByteCode:Annotation:Assert)-[:OF_TYPE]->(:Java:ByteCode:Type {fqn: 'AnnotationType'})"
);
}

@Test
Expand Down Expand Up @@ -69,8 +72,42 @@ void javaAssertMethod() throws RuleException {
}

@Test
void javaTestMethodWithoutAssertion() throws RuleException {
void javaAssertAnnotation() throws RuleException {
Result<Concept> result = applyConcept("java:AssertAnnotation");
assertThat(result.getStatus()).isEqualTo(SUCCESS);
assertThat(result.getRows()).hasSize(1);
store.beginTransaction();
Map<String, Column<?>> columns = result.getRows()
.get(0)
.getColumns();
assertThat(columns.get("DeclaringType").getLabel()).isEqualTo("Test");
assertThat(columns.get("AnnotatedTestMethod").getLabel()).isEqualTo("void annotatedTest()");
assertThat(columns.get("AnnotationType").getLabel()).isEqualTo("AnnotationType");
store.commitTransaction();
}

@Test
void javaMethodPerformsAssertion() throws RuleException {
Result<Concept> result = applyConcept("java:MethodPerformsAssertion");
assertThat(result.getStatus()).isEqualTo(SUCCESS);
assertThat(result.getRows()).hasSize(2);
Map<String, Column<?>> annotationAssertion = result.getRows().get(0).getColumns();
Map<String, Column<?>> methodAssertion = result.getRows().get(1).getColumns();
store.beginTransaction();
assertThat(annotationAssertion.get("DeclaringType").getLabel()).isEqualTo("Test");
assertThat(annotationAssertion.get("Method").getLabel()).isEqualTo("void annotatedTest()");
assertThat(methodAssertion.get("DeclaringType").getLabel()).isEqualTo("Test");
assertThat(methodAssertion.get("Method").getLabel()).isEqualTo("void test()");
store.commitTransaction();
}

@Test
void javaTestMethodAssertionWithinCallHierarchy() throws RuleException {
assertThat(validateConstraint("java:TestMethodWithoutAssertion").getStatus()).isEqualTo(SUCCESS);
}

@Test
void javaTestMethodAssertionOutOfCallHierarchy() throws RuleException {
Result<Constraint> result = validateConstraint("java:TestMethodWithoutAssertion", Map.of("javaTestAssertMaxCallDepth", "1"));
assertThat(result.getStatus()).isEqualTo(FAILURE);
assertThat(result.getRows()).hasSize(1);
Expand All @@ -82,4 +119,9 @@ void javaTestMethodWithoutAssertion() throws RuleException {
assertThat(((Column<MethodDescriptor>) columns.get("TestMethod")).getValue()).isNotNull();
store.commitTransaction();
}

@Test
void javaTestMethodAssertionViaAnnotation() throws RuleException {
assertThat(validateConstraint("java:TestMethodWithoutAssertion").getStatus()).isEqualTo(SUCCESS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,14 @@
<constraint id="junit:TestMethodWithoutAssertion">
<requiresConcept refId="java:VirtualInvokes"/>
<requiresConcept refId="java:TestMethod"/>
<requiresConcept refId="java:AssertMethod"/>
<requiresConcept refId="java:MethodPerformsAssertion"/>
<description>All test methods must perform assertions (within a call hierarchy of max. 3 steps).</description>
<deprecated>This constraint has been replaced by "java:TestMethodWithoutAssertion".</deprecated>
<cypher><![CDATA[
MATCH
(testType:Type)-[:DECLARES]->(testMethod:Test:Method)
WHERE
NOT (testMethod)-[:INVOKES|VIRTUAL_INVOKES*..3]->(:Method:Assert)
NOT (testMethod)-[:PERFORMS_ASSERTION]->(:Assert)
RETURN
testType AS DeclaringType,
testMethod AS Method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,23 @@
]]></cypher>
</concept>

<concept id="junit4:AssertAnnotation">
<providesConcept refId="java:AssertAnnotation"/>
<requiresConcept refId="java:TestMethod"/>
<description>Labels @Test-Annotations with the parameter "expected" as assert annotations.</description>
<cypher><![CDATA[
MATCH
(testType:Java:Type)-[:DECLARES]->(annotatedTestMethod:Test:Method)-[:ANNOTATED_BY]->(annotation:Annotation)-[:OF_TYPE]->(:Java:Type {fqn: "org.junit.Test"}),
(annotation)-[:HAS]->(expectation:Java:Value {name: 'expected'})
SET
annotation:Junit4:Assert
RETURN
testType AS DeclaringType, annotatedTestMethod AS AnnotatedTestMethod
ORDER BY
testType.fqn, annotatedTestMethod.name
]]></cypher>
</concept>

<concept id="junit4:SuiteClass">
<description>Labels all classes annotated by "@org.junit.runners.Suite.SuiteClasses" with "Junit4" and "Suite" and creates a relation "CONTAINS_TESTCLASS" to all referenced classes.</description>
<cypher><![CDATA[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import java.util.Map;
import java.util.stream.Collectors;

import com.buschmais.jqassistant.core.report.api.model.Column;
import com.buschmais.jqassistant.core.report.api.model.Result;
import com.buschmais.jqassistant.core.report.api.model.Row;
import com.buschmais.jqassistant.core.rule.api.model.Concept;
import com.buschmais.jqassistant.core.rule.api.model.Constraint;
import com.buschmais.jqassistant.core.rule.api.model.RuleException;
import com.buschmais.jqassistant.core.shared.map.MapBuilder;
Expand Down Expand Up @@ -176,6 +178,29 @@ public void assertMethod() throws Exception {
store.commitTransaction();
}

/**
* Verifies the concept "junit4:AssertAnnotation".
*
* @throws IOException
* If the test fails.
* @throws NoSuchMethodException
* If the test fails.
*/
@Test
public void assertAnnotation() throws Exception {
scanClasses(Assertions4Junit4.class);
Result<Concept> result = applyConcept("java:AssertAnnotation");
store.beginTransaction();
assertThat(result.getStatus(), equalTo(SUCCESS));
Map<String, Column<?>> assertAnnotation = result.getRows()
.get(0)
.getColumns();
assertThat(assertAnnotation.get("DeclaringType").getLabel(), endsWith("test.set.junit4.Assertions4Junit4"));
assertThat(assertAnnotation.get("AnnotatedTestMethod").getLabel(), equalTo("void testWithExpectedRuntimeException()"));
assertThat(assertAnnotation.get("AnnotationType").getLabel(), equalTo("org.junit.Test"));
store.commitTransaction();
}

/**
* Verifies the concept "junit4:BeforeMethod".
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,11 @@ public void nonJUnit5TestMethod() throws Exception {
.get("TestMethod")
.getValue())
.collect(Collectors.toList());
assertThat(rows.size(), equalTo(5));
assertThat(rows.size(), equalTo(6));
assertThat(rows, containsInAnyOrder(is(methodDescriptor(Assertions4Junit4.class, "assertWithoutMessage")),
is(methodDescriptor(Assertions4Junit4.class, "assertWithMessage")), is(methodDescriptor(Assertions4Junit4.class, "testWithoutAssertion")),
is(methodDescriptor(Assertions4Junit4.class, "testWithAssertion")), is(methodDescriptor(Assertions4Junit4.class, "testWithNestedAssertion"))));
is(methodDescriptor(Assertions4Junit4.class, "testWithAssertion")), is(methodDescriptor(Assertions4Junit4.class, "testWithNestedAssertion")),
is(methodDescriptor(Assertions4Junit4.class, "testWithExpectedRuntimeException"))));
store.commitTransaction();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public void testWithNestedAssertion() {
nestedAssertion();
}

@Test(expected = RuntimeException.class)
public void testWithExpectedRuntimeException(){};

private void nestedAssertion() {
fail("Failing");
}
Expand Down

0 comments on commit 36e9a53

Please sign in to comment.