
org.sonar.java.checks.helpers.UnitTestUtils Maven / Gradle / Ivy
/*
* SonarQube Java
* Copyright (C) 2012-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks.helpers;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.semantic.MethodMatchers;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Tree;
import static java.util.Arrays.asList;
public final class UnitTestUtils {
private static final String ORG_JUNIT_TEST = "org.junit.Test";
public static final Pattern ASSERTION_METHODS_PATTERN = Pattern.compile(
"(assert|verify|fail|should|check|expect|validate|andExpect).*" +
// Eclipse Vert.x with JUnit 5 (VertxTestContext)
"|laxCheckpoint|succeedingThenComplete");
private static final Pattern TEST_METHODS_PATTERN = Pattern.compile("test.*|.*Test");
public static final MethodMatchers ASSERTION_INVOCATION_MATCHERS = MethodMatchers.or(
// fest 1.x / 2.X
MethodMatchers.create().ofSubTypes("org.fest.assertions.GenericAssert", "org.fest.assertions.api.AbstractAssert").anyName().withAnyParameters().build(),
// rest assured 2.x, 3.x, 4.x
MethodMatchers.create().ofTypes(
"com.jayway.restassured.response.ValidatableResponseOptions", // restassured 2.x
"io.restassured.response.ValidatableResponseOptions", // restassured 3.x and 4.x
"io.restassured.module.mockmvc.response.ValidatableMockMvcResponse" // spring mock mvc extending the io.restassured library
)
.name(name -> "body".equals(name) ||
"time".equals(name) ||
name.startsWith("time") ||
name.startsWith("content") ||
name.startsWith("status") ||
name.startsWith("header") ||
name.startsWith("cookie") ||
name.startsWith("spec"))
.withAnyParameters()
.build(),
// assertJ
MethodMatchers.create().ofSubTypes("org.assertj.core.api.AbstractAssert").anyName().withAnyParameters().build(),
MethodMatchers.create().ofSubTypes("org.assertj.core.api.ThrowableTypeAssert").anyName().withAnyParameters().build(),
// spring
MethodMatchers.create().ofTypes("org.springframework.test.web.servlet.ResultActions").names("andExpect", "andExpectAll").withAnyParameters().build(),
// JMockit
MethodMatchers.create().ofTypes("mockit.Verifications").constructor().withAnyParameters().build(),
// Eclipse Vert.x with JUnit 4
MethodMatchers.create().ofTypes("io.vertx.ext.unit.TestContext").name(name -> name.startsWith("asyncAssert")).addWithoutParametersMatcher().build(),
// Awaitility
MethodMatchers.create().ofTypes("org.awaitility.core.ConditionFactory").name(name -> name.startsWith("until")).withAnyParameters().build());
public static final MethodMatchers REACTIVE_X_TEST_METHODS = MethodMatchers.create().ofSubTypes("rx.Observable", "io.reactivex.Observable").names("test").withAnyParameters()
.build();
private static final MethodMatchers VERTX_TEST_CONTEXT_METHODS = MethodMatchers.create().ofTypes("io.vertx.junit5.VertxTestContext").anyName().withAnyParameters().build();
public static final MethodMatchers FAIL_METHOD_MATCHER = MethodMatchers.or(
MethodMatchers.create().ofTypes(
// JUnit 5
"org.junit.jupiter.api.Assertions",
// JUnit 4
"org.junit.Assert",
// JUnit 3
"junit.framework.Assert",
// Fest assert
"org.fest.assertions.Fail",
// AssertJ
"org.assertj.core.api.Fail",
"org.assertj.core.api.Assertions")
.names("fail").withAnyParameters().build(),
MethodMatchers.create().ofTypes(
// AssertJ
"org.assertj.core.api.Assertions")
.names("failBecauseExceptionWasNotThrown").withAnyParameters().build());
public static final MethodMatchers ASSERTIONS_METHOD_MATCHER = MethodMatchers.or(
// JUnit 3, 4 and 5
MethodMatchers.create()
.ofTypes("org.junit.Assert", "org.junit.jupiter.api.Assertions", "junit.framework.Assert", "junit.framework.TestCase")
.name(name -> name.startsWith("assert"))
.withAnyParameters()
.build(),
// Fest assert and AssertJ
MethodMatchers.create()
.ofTypes("org.assertj.core.api.Assertions", "org.fest.assertions.Assertions")
.names("assertThat")
.withAnyParameters()
.build());
/**
* Match when we are sure that the intention is to assert something, that will result in an AssertionError if the assertion fails.
* The purpose is not to detect any assertion method (similar to S2699).
*/
public static final MethodMatchers COMMON_ASSERTION_MATCHER = MethodMatchers.or(
FAIL_METHOD_MATCHER, ASSERTIONS_METHOD_MATCHER);
private static final Set TEST_ANNOTATIONS = new HashSet<>(asList(ORG_JUNIT_TEST, "org.testng.annotations.Test"));
private static final Set JUNIT5_TEST_ANNOTATIONS = new HashSet<>(asList(
"org.junit.jupiter.api.Test",
"org.junit.jupiter.api.RepeatedTest",
"org.junit.jupiter.api.TestFactory",
"org.junit.jupiter.api.TestTemplate",
"org.junit.jupiter.params.ParameterizedTest"));
private static final String NESTED_ANNOTATION = "org.junit.jupiter.api.Nested";
private static final Pattern UNIT_TEST_NAME_RELATED_TO_OBJECT_METHODS_REGEX = Pattern.compile("equal|hash_?code|object_?method|to_?string", Pattern.CASE_INSENSITIVE);
private UnitTestUtils() {
}
public static boolean hasNestedAnnotation(ClassTree tree) {
SymbolMetadata metadata = tree.symbol().metadata();
return metadata.isAnnotatedWith(NESTED_ANNOTATION);
}
public static boolean hasTestAnnotation(MethodTree tree) {
SymbolMetadata symbolMetadata = tree.symbol().metadata();
return TEST_ANNOTATIONS.stream().anyMatch(symbolMetadata::isAnnotatedWith) || hasJUnit5TestAnnotation(symbolMetadata);
}
public static boolean hasJUnit5TestAnnotation(MethodTree tree) {
return hasJUnit5TestAnnotation(tree.symbol().metadata());
}
private static boolean hasJUnit5TestAnnotation(SymbolMetadata symbolMetadata) {
return JUNIT5_TEST_ANNOTATIONS.stream().anyMatch(symbolMetadata::isAnnotatedWith);
}
public static boolean isInUnitTestRelatedToObjectMethods(ExpressionTree expr) {
return isUnitTestRelatedToObjectMethods(ExpressionUtils.getEnclosingMethod(expr));
}
public static boolean isUnitTestRelatedToObjectMethods(@Nullable MethodTree method) {
return method != null && UNIT_TEST_NAME_RELATED_TO_OBJECT_METHODS_REGEX.matcher(method.simpleName().name()).find();
}
public static boolean isUnitTest(MethodTree methodTree) {
if (isOrOverridesJunit4TestMethod(methodTree)) {
return true;
}
if (hasJUnit5TestAnnotation(methodTree)) {
// contrary to JUnit 4, JUnit 5 Test annotations are not inherited when method is overridden, so no need to check overridden symbols
return true;
}
Symbol.TypeSymbol enclosingClass = Objects.requireNonNull(methodTree.symbol().enclosingClass(), "Must not be null for method symbols");
return enclosingClass.type().isSubtypeOf("junit.framework.TestCase") && methodTree.simpleName().name().startsWith("test");
}
private static boolean isOrOverridesJunit4TestMethod(MethodTree methodTree) {
Symbol.MethodSymbol symbol = methodTree.symbol();
return symbol.metadata().isAnnotatedWith(ORG_JUNIT_TEST)
// JUnit 4 considers as test any method overriding another annotated with @Test
|| symbol.overriddenSymbols().stream()
.map(Symbol::metadata)
.anyMatch(meta -> meta.isAnnotatedWith(ORG_JUNIT_TEST));
}
public static boolean isTestClass(ClassTree classTree) {
Symbol.TypeSymbol classSymbol = classTree.symbol();
return !classSymbol.isAbstract()
&& isTopLevelClass(classSymbol)
&& (hasTestMethod(classTree.members()) || hasNestedClass(classTree));
}
private static boolean isTopLevelClass(Symbol.TypeSymbol classSymbol) {
return classSymbol.owner().isPackageSymbol();
}
private static boolean hasTestMethod(List members) {
return members.stream()
.filter(member -> member.is(Tree.Kind.METHOD))
.map(MethodTree.class::cast)
.anyMatch(UnitTestUtils::hasTestAnnotation);
}
private static boolean hasNestedClass(ClassTree classTree) {
return classTree.members()
.stream()
.filter(member -> member.is(Tree.Kind.CLASS))
.map(ClassTree.class::cast)
.anyMatch(UnitTestUtils::hasNestedAnnotation);
}
public static boolean methodNameMatchesAssertionMethodPattern(String methodName, Symbol methodSymbol) {
if (TEST_METHODS_PATTERN.matcher(methodName).matches()) {
return !REACTIVE_X_TEST_METHODS.matches(methodSymbol);
}
if ("verify".equals(methodName) || "failing".equals(methodName)) {
return !VERTX_TEST_CONTEXT_METHODS.matches(methodSymbol);
}
return ASSERTION_METHODS_PATTERN.matcher(methodName).matches();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy