/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.java.rule.junit; import java.util.HashMap; import java.util.List; import java.util.Map; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTMemberValuePair; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTNormalAnnotation; 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.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.lang.symboltable.Scope; public class JUnitTestsShouldIncludeAssertRule extends AbstractJUnitRule { @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { if (node.isInterface()) { return data; } return super.visit(node, data); } @Override public Object visit(ASTMethodDeclaration method, Object data) { if (isJUnitMethod(method, data)) { if (!isExpectAnnotated(method.jjtGetParent())) { Scope classScope = method.getScope().getParent(); Map<String, List<NameOccurrence>> expectables = getRuleAnnotatedExpectedExceptions(classScope); if (!containsExpectOrAssert(method.getBlock(), expectables)) { addViolation(data, method); } } } return data; } private boolean containsExpectOrAssert(Node n, Map<String, List<NameOccurrence>> expectables) { if (n instanceof ASTStatementExpression) { if (isExpectStatement((ASTStatementExpression) n, expectables) || isAssertOrFailStatement((ASTStatementExpression) n)) { return true; } } else { for (int i = 0; i < n.jjtGetNumChildren(); i++) { Node c = n.jjtGetChild(i); if (containsExpectOrAssert(c, expectables)) { return true; } } } return false; } /** * Gets a list of NameDeclarations for all the fields that have type * ExpectedException and have a Rule annotation. * * @param classScope * The class scope to search for * @return See description */ private Map<String, List<NameOccurrence>> getRuleAnnotatedExpectedExceptions(Scope classScope) { Map<String, List<NameOccurrence>> result = new HashMap<>(); Map<NameDeclaration, List<NameOccurrence>> decls = classScope.getDeclarations(); for (NameDeclaration decl : decls.keySet()) { Node parent = decl.getNode().jjtGetParent().jjtGetParent().jjtGetParent(); if (parent.hasDescendantOfType(ASTAnnotation.class) && parent.getFirstChildOfType(ASTFieldDeclaration.class) != null) { String annot = parent.getFirstDescendantOfType(ASTMarkerAnnotation.class).jjtGetChild(0).getImage(); if (!"Rule".equals(annot) && !"org.junit.Rule".equals(annot)) { continue; } Node type = parent.getFirstDescendantOfType(ASTReferenceType.class); if (!"ExpectedException".equals(type.jjtGetChild(0).getImage())) { continue; } result.put(decl.getName(), decls.get(decl)); } } return result; } /** * Tells if the node contains a Test annotation with an expected exception. */ private boolean isExpectAnnotated(Node methodParent) { List<ASTNormalAnnotation> annotations = methodParent.findDescendantsOfType(ASTNormalAnnotation.class); for (ASTNormalAnnotation annotation : annotations) { ASTName name = annotation.getFirstChildOfType(ASTName.class); if (name != null && ("Test".equals(name.getImage()) || name.getType() != null && name.getType().equals(JUNIT4_CLASS))) { List<ASTMemberValuePair> memberValues = annotation.findDescendantsOfType(ASTMemberValuePair.class); for (ASTMemberValuePair pair : memberValues) { if ("expected".equals(pair.getImage())) { return true; } } } } return false; } /** * Tells if the expression is an assert statement or not. */ private boolean isAssertOrFailStatement(ASTStatementExpression expression) { 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 != null && (img.startsWith("assert") || img.startsWith("fail") || img.startsWith("Assert.assert") || img.startsWith("Assert.fail"))) { return true; } } } } return false; } private boolean isExpectStatement(ASTStatementExpression expression, Map<String, List<NameOccurrence>> expectables) { if (expression != null) { ASTPrimaryExpression pe = expression.getFirstChildOfType(ASTPrimaryExpression.class); if (pe != null) { Node name = pe.getFirstDescendantOfType(ASTName.class); // case of an AllocationExpression if (name == null) { return false; } String img = name.getImage(); if (img.indexOf(".") == -1) { return false; } String varname = img.split("\\.")[0]; if (!expectables.containsKey(varname)) { return false; } for (NameOccurrence occ : expectables.get(varname)) { if (occ.getLocation() == name && img.startsWith(varname + ".expect")) { return true; } } } } return false; } }