Make AssertJ soft assertions count as assertions
This commit is contained in:
@ -176,5 +176,10 @@
|
||||
<artifactId>ant-testutil</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -20,6 +20,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule;
|
||||
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameDeclaration;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
|
||||
@ -39,10 +40,12 @@ public class JUnitTestsShouldIncludeAssertRule extends AbstractJUnitRule {
|
||||
public Object visit(ASTMethodDeclaration method, Object data) {
|
||||
if (isJUnitMethod(method, data)) {
|
||||
if (!isExpectAnnotated(method.jjtGetParent())) {
|
||||
Map<String, VariableNameDeclaration> variables = getVariables(method);
|
||||
|
||||
Scope classScope = method.getScope().getParent();
|
||||
Map<String, List<NameOccurrence>> expectables = getRuleAnnotatedExpectedExceptions(classScope);
|
||||
|
||||
if (!containsExpectOrAssert(method.getBlock(), expectables)) {
|
||||
|
||||
if (!containsExpectOrAssert(method.getBlock(), expectables, variables)) {
|
||||
addViolation(data, method);
|
||||
}
|
||||
}
|
||||
@ -50,24 +53,35 @@ public class JUnitTestsShouldIncludeAssertRule extends AbstractJUnitRule {
|
||||
return data;
|
||||
}
|
||||
|
||||
private boolean containsExpectOrAssert(Node n, Map<String, List<NameOccurrence>> expectables) {
|
||||
private boolean containsExpectOrAssert(Node n,
|
||||
Map<String, List<NameOccurrence>> expectables,
|
||||
Map<String, VariableNameDeclaration> variables) {
|
||||
if (n instanceof ASTStatementExpression) {
|
||||
if (isExpectStatement((ASTStatementExpression) n, expectables)
|
||||
|| isAssertOrFailStatement((ASTStatementExpression) n)
|
||||
|| isVerifyStatement((ASTStatementExpression) n)) {
|
||||
|| isVerifyStatement((ASTStatementExpression) n)
|
||||
|| isSoftAssertionStatement((ASTStatementExpression) n, variables)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0; i < n.jjtGetNumChildren(); i++) {
|
||||
Node c = n.jjtGetChild(i);
|
||||
if (containsExpectOrAssert(c, expectables)) {
|
||||
if (containsExpectOrAssert(c, expectables, variables)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private Map<String, VariableNameDeclaration> getVariables(ASTMethodDeclaration method) {
|
||||
Map<String, VariableNameDeclaration> variables = new HashMap<>();
|
||||
for (VariableNameDeclaration vnd : method.getScope().getDeclarations(VariableNameDeclaration.class).keySet()) {
|
||||
variables.put(vnd.getName(), vnd);
|
||||
}
|
||||
return variables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of NameDeclarations for all the fields that have type
|
||||
* ExpectedException and have a Rule annotation.
|
||||
@ -189,4 +203,30 @@ public class JUnitTestsShouldIncludeAssertRule extends AbstractJUnitRule {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSoftAssertionStatement(ASTStatementExpression expression,
|
||||
Map<String, VariableNameDeclaration> variables) {
|
||||
if (expression != null) {
|
||||
ASTPrimaryExpression pe = expression.getFirstChildOfType(ASTPrimaryExpression.class);
|
||||
if (pe != null) {
|
||||
Node name = pe.getFirstDescendantOfType(ASTName.class);
|
||||
if (name != null) {
|
||||
String img = name.getImage();
|
||||
if (img.indexOf(".") == -1) {
|
||||
return false;
|
||||
}
|
||||
String[] tokens = img.split("\\.");
|
||||
String methodName = tokens[1];
|
||||
boolean methodIsAssertAll = "assertAll".equals(methodName);
|
||||
|
||||
String varName = tokens[0];
|
||||
boolean variableTypeIsSoftAssertion = variables.containsKey(varName)
|
||||
&& TypeHelper.isA(variables.get(varName), "org.assertj.core.api.AbstractSoftAssertions");
|
||||
|
||||
return methodIsAssertAll && variableTypeIsSoftAssertion;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -54,23 +54,29 @@ public final class TypeHelper {
|
||||
|
||||
private static Class<?> loadClassWithNodeClassloader(final TypeNode n, final String clazzName) {
|
||||
if (n.getType() != null) {
|
||||
try {
|
||||
ClassLoader classLoader = n.getType().getClassLoader();
|
||||
if (classLoader == null) {
|
||||
// Using the system classloader then
|
||||
classLoader = ClassLoader.getSystemClassLoader();
|
||||
}
|
||||
|
||||
// If the requested type is in the classpath, using the same classloader should work
|
||||
return ClassUtils.getClass(classLoader, clazzName);
|
||||
} catch (final ClassNotFoundException ignored) {
|
||||
// The requested type is not on the auxclasspath. This might happen, if the type node
|
||||
// is probed for a specific type (e.g. is is a JUnit5 Test Annotation class).
|
||||
// Failing to resolve clazzName does not necessarily indicate an incomplete auxclasspath.
|
||||
} catch (final LinkageError expected) {
|
||||
// We found the class but it's invalid / incomplete. This may be an incomplete auxclasspath
|
||||
// if it was a NoClassDefFoundError. TODO : Report it?
|
||||
return loadClass(n.getType().getClassLoader(), clazzName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Class<?> loadClass(final ClassLoader nullableClassLoader, final String clazzName) {
|
||||
try {
|
||||
ClassLoader classLoader = nullableClassLoader;
|
||||
if (classLoader == null) {
|
||||
// Using the system classloader then
|
||||
classLoader = ClassLoader.getSystemClassLoader();
|
||||
}
|
||||
|
||||
// If the requested type is in the classpath, using the same classloader should work
|
||||
return ClassUtils.getClass(classLoader, clazzName);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// The requested type is not on the auxclasspath. This might happen, if the type node
|
||||
// is probed for a specific type (e.g. is is a JUnit5 Test Annotation class).
|
||||
// Failing to resolve clazzName does not necessarily indicate an incomplete auxclasspath.
|
||||
} catch (final LinkageError expected) {
|
||||
// We found the class but it's invalid / incomplete. This may be an incomplete auxclasspath
|
||||
// if it was a NoClassDefFoundError. TODO : Report it?
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -133,4 +139,13 @@ public final class TypeHelper {
|
||||
|
||||
return clazz.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
public static boolean isA(TypedNameDeclaration vnd, String className) {
|
||||
Class<?> type = vnd.getType();
|
||||
if (type != null) {
|
||||
Class<?> expected = loadClass(type.getClassLoader(), className);
|
||||
return expected.isAssignableFrom(type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -429,6 +429,48 @@ class Style {
|
||||
public void moveOutOfBoundsFrom() {
|
||||
doSomething();
|
||||
}
|
||||
}]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#1435 Treat AssertJ soft assertions as assert expressions</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class FooTest {
|
||||
|
||||
@Test
|
||||
void testFoo() {
|
||||
var softly = new SoftAssertions();
|
||||
softly.assertThat("doesn't matter").isEqualTo("doesn't matter");
|
||||
softly.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testBar() {
|
||||
SoftAssertions softly = new SoftAssertions();
|
||||
softly.assertThat("doesn't matter").isEqualTo("doesn't matter");
|
||||
softly.assertAll();
|
||||
}
|
||||
}]]></code>
|
||||
</test-code>
|
||||
<test-code>
|
||||
<description>#1435 Treat AssertJ soft assertion rule for JUnit 4 as assert expressions</description>
|
||||
<expected-problems>0</expected-problems>
|
||||
<code><![CDATA[
|
||||
import org.assertj.core.api.JUnitSoftAssertions;
|
||||
import org.junit.Test;
|
||||
|
||||
public class FooTest {
|
||||
|
||||
@Rule
|
||||
public final JUnitSoftAssertions softly = new JUnitSoftAssertions();
|
||||
|
||||
@Test
|
||||
void testFoo() {
|
||||
softly.assertThat("doesn't matter").isEqualTo("doesn't matter");
|
||||
}
|
||||
}]]></code>
|
||||
</test-code>
|
||||
</test-data>
|
||||
|
5
pom.xml
5
pom.xml
@ -936,6 +936,11 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
||||
<artifactId>system-rules</artifactId>
|
||||
<version>1.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.11.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TEST DEPENDENCIES -->
|
||||
|
||||
|
Reference in New Issue
Block a user