mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
junit 5 malformed parameterized sources inspection
This commit is contained in:
+1
@@ -2214,4 +2214,5 @@ string.concatenation.introduce.fix.name=Introduce new {1} to update variable ''{
|
||||
|
||||
ignored.class.names=Ignore classes (including subclasses)
|
||||
junit5.platform.runner.display.name=@RunWith(JUnitPlatform.class) without test methods
|
||||
junit5.valid.parameterized.configuration.display.name=JUnit 5 malformed parameterized sources
|
||||
|
||||
|
||||
+7
@@ -26,4 +26,11 @@ public class JUnitCommonClassNames {
|
||||
public static final String ORG_JUNIT_TEST = "org.junit.Test";
|
||||
public static final String ORG_JUNIT_RULE = "org.junit.Rule";
|
||||
public static final String ORG_JUNIT_CLASS_RULE = "org.junit.ClassRule";
|
||||
public static final String ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST = "org.junit.jupiter.params.ParameterizedTest";
|
||||
public static final String ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE = "org.junit.jupiter.params.provider.MethodSource";
|
||||
public static final String ORG_JUNIT_JUPITER_PARAMS_VALUES_SOURCE = "org.junit.jupiter.params.provider.ValueSource";
|
||||
public static final String ORG_JUNIT_JUPITER_PARAMS_ENUM_SOURCE = "org.junit.jupiter.params.provider.EnumSource";
|
||||
public static final String ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS = "org.junit.jupiter.params.provider.Arguments";
|
||||
public static final String ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_SOURCE = "org.junit.jupiter.params.provider.CsvSource";
|
||||
public static final String ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_FILE_SOURCE = "org.junit.jupiter.params.provider.CsvFileSource";
|
||||
}
|
||||
|
||||
@@ -77,6 +77,11 @@
|
||||
<implicitUsageProvider implementation="com.intellij.execution.junit2.inspection.JUnitImplicitUsageProvider"/>
|
||||
<predefinedMigrationMapProvider implementation="com.intellij.execution.junit2.refactoring.JUnit5Migration"/>
|
||||
<psi.referenceContributor implementation="com.intellij.execution.junit.codeInsight.references.JUnitReferenceContributor"/>
|
||||
|
||||
<localInspection groupPath="Java" language="JAVA" shortName="JUnit5MalformedParameterized" bundle="com.siyeh.InspectionGadgetsBundle"
|
||||
key="junit5.valid.parameterized.configuration.display.name"
|
||||
groupBundle="messages.InspectionsBundle" groupKey="group.names.junit.issues" enabledByDefault="true" level="WARNING"
|
||||
implementationClass="com.intellij.execution.junit.codeInsight.JUnit5MalformedParameterizedInspection"/>
|
||||
</extensions>
|
||||
|
||||
<extensionPoints>
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.execution.junit.codeInsight
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil
|
||||
import com.intellij.codeInsight.intention.QuickFixFactory
|
||||
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.execution.junit.codeInsight.references.MethodSourceReference
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion
|
||||
import com.intellij.openapi.projectRoots.JavaVersionService
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.InheritanceUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.siyeh.InspectionGadgetsBundle
|
||||
import com.siyeh.ig.junit.JUnitCommonClassNames
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
class JUnit5MalformedParameterizedInspection : BaseJavaBatchLocalInspectionTool() {
|
||||
|
||||
@Nls
|
||||
override fun getDisplayName(): String {
|
||||
return InspectionGadgetsBundle.message("junit5.valid.parameterized.configuration.display.name")
|
||||
}
|
||||
|
||||
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
val file = holder.file
|
||||
if (!JavaVersionService.getInstance().isAtLeast(file, JavaSdkVersion.JDK_1_8)) return PsiElementVisitor.EMPTY_VISITOR
|
||||
if (JavaPsiFacade.getInstance(file.project).findClass(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST, file.resolveScope) == null) {
|
||||
return PsiElementVisitor.EMPTY_VISITOR
|
||||
}
|
||||
return object : JavaElementVisitor() {
|
||||
|
||||
override fun visitMethod(method: PsiMethod) {
|
||||
val modifierList = method.modifierList
|
||||
val parameterizedAnnotation = modifierList.findAnnotation(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PARAMETERIZED_TEST)
|
||||
if (parameterizedAnnotation != null) {
|
||||
val methodSource = modifierList.findAnnotation(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_METHOD_SOURCE)
|
||||
if (methodSource != null) {
|
||||
checkMethodSource(method, methodSource)
|
||||
}
|
||||
|
||||
val valuesSource = modifierList.findAnnotation(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_VALUES_SOURCE)
|
||||
if (valuesSource != null) {
|
||||
checkValuesSource(method, valuesSource)
|
||||
}
|
||||
|
||||
val enumSource = modifierList.findAnnotation(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_ENUM_SOURCE)
|
||||
if (enumSource != null) {
|
||||
checkEnumSource(method, enumSource)
|
||||
}
|
||||
|
||||
val csvFileSource = modifierList.findAnnotation(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_FILE_SOURCE)
|
||||
val csvSource = modifierList.findAnnotation(JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_CSV_SOURCE)
|
||||
|
||||
val noMultiArgsProvider = methodSource == null && csvFileSource == null && csvSource == null
|
||||
|
||||
if (valuesSource == null && enumSource == null && noMultiArgsProvider) {
|
||||
holder.registerProblem(parameterizedAnnotation, "No sources are provided, the suite would be empty")
|
||||
}
|
||||
else if (method.parameterList.parametersCount > 1 && noMultiArgsProvider) {
|
||||
holder.registerProblem(valuesSource ?: enumSource!!, "Multiple parameters are not supported by this source")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkEnumSource(method: PsiMethod, enumSource: PsiAnnotation) {
|
||||
processArrayInAnnotationParameter(enumSource.findAttributeValue("value"), { value ->
|
||||
if (value is PsiClassObjectAccessExpression) {
|
||||
checkSourceTypeAndParameterTypeAgree(method, value, value.operand.type)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun checkValuesSource(method: PsiMethod, valuesSource: PsiAnnotation) {
|
||||
val possibleValues = ContainerUtil.immutableMapBuilder<String, PsiType>()
|
||||
.put("strings", PsiType.getJavaLangString(method.manager, method.resolveScope))
|
||||
.put("ints", PsiType.INT)
|
||||
.put("longs", PsiType.LONG)
|
||||
.put("doubles", PsiType.DOUBLE).build()
|
||||
|
||||
for (valueKey in possibleValues.keys) {
|
||||
processArrayInAnnotationParameter(valuesSource.findDeclaredAttributeValue(valueKey),
|
||||
{ value -> checkSourceTypeAndParameterTypeAgree(method, value, possibleValues[valueKey]!!) })
|
||||
}
|
||||
|
||||
val attributesNumber = valuesSource.parameterList.attributes.size
|
||||
if (attributesNumber > 1) {
|
||||
holder.registerProblem(valuesSource, "Exactly one type of input must be provided")
|
||||
}
|
||||
else if (attributesNumber == 0) {
|
||||
holder.registerProblem(valuesSource, "No value source is defined")
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkMethodSource(method: PsiMethod, methodSource: PsiAnnotation) {
|
||||
val annotationMemberValue = methodSource.findDeclaredAttributeValue("names")
|
||||
processArrayInAnnotationParameter(annotationMemberValue, { attributeValue ->
|
||||
for (reference in attributeValue.references) {
|
||||
if (reference is MethodSourceReference) {
|
||||
val resolve = reference.resolve()
|
||||
if (resolve !is PsiMethod) {
|
||||
holder.registerProblem(attributeValue,
|
||||
"Cannot resolve target method source: \'" + reference.value + "\'")
|
||||
}
|
||||
else {
|
||||
val sourceProvider : PsiMethod = resolve
|
||||
val providerName = sourceProvider.name
|
||||
|
||||
if (!sourceProvider.hasModifierProperty(PsiModifier.STATIC)) {
|
||||
holder.registerProblem(attributeValue, "Method source \'$providerName\' must be static",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
QuickFixFactory.getInstance().createModifierListFix(sourceProvider, PsiModifier.STATIC, true, false))
|
||||
}
|
||||
else if (sourceProvider.parameterList.parametersCount != 0) {
|
||||
holder.registerProblem(attributeValue, "Method source \'$providerName\' should have no parameters")
|
||||
}
|
||||
else {
|
||||
val componentType = getComponentType(sourceProvider.returnType, method)
|
||||
if (componentType == null) {
|
||||
holder.registerProblem(attributeValue,
|
||||
"Method source \'$providerName\' must have one of the following return type: Stream<?>, Iterator<?>, Iterable<?> or Object[]")
|
||||
}
|
||||
else if (method.parameterList.parametersCount > 1 && !isArgumentsInheritor(componentType)) {
|
||||
holder.registerProblem(attributeValue, "Multiple parameters have to be wrapped in Arguments")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun processArrayInAnnotationParameter(attributeValue: PsiAnnotationMemberValue?,
|
||||
checker: (value : PsiAnnotationMemberValue) -> Unit) {
|
||||
if (attributeValue is PsiLiteral || attributeValue is PsiClassObjectAccessExpression) {
|
||||
checker.invoke(attributeValue)
|
||||
}
|
||||
else if (attributeValue is PsiArrayInitializerMemberValue) {
|
||||
for (memberValue in attributeValue.initializers) {
|
||||
processArrayInAnnotationParameter(memberValue, checker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSourceTypeAndParameterTypeAgree(method: PsiMethod,
|
||||
attributeValue: PsiAnnotationMemberValue,
|
||||
componentType: PsiType) {
|
||||
val parameters = method.parameterList.parameters
|
||||
if (parameters.size == 1 && !parameters[0].type.isAssignableFrom(componentType) && !isArgumentsInheritor(componentType)) {
|
||||
holder.registerProblem(attributeValue,
|
||||
"No implicit conversion found to convert object of type " + componentType.presentableText + " to " + parameters[0].type.presentableText)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isArgumentsInheritor(componentType: PsiType): Boolean {
|
||||
return InheritanceUtil.isInheritor(componentType, JUnitCommonClassNames.ORG_JUNIT_JUPITER_PARAMS_PROVIDER_ARGUMENTS)
|
||||
}
|
||||
|
||||
private fun getComponentType(returnType: PsiType?, method: PsiMethod): PsiType? {
|
||||
val collectionItemType = JavaGenericsUtil.getCollectionItemType(returnType, method.resolveScope)
|
||||
if (collectionItemType != null) {
|
||||
return collectionItemType
|
||||
}
|
||||
|
||||
val streamItemType = PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_STREAM_STREAM, 1, false)
|
||||
if (streamItemType != null) {
|
||||
return streamItemType
|
||||
}
|
||||
|
||||
return PsiUtil.substituteTypeParameter(returnType, CommonClassNames.JAVA_UTIL_ITERATOR, 1, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports parameterized tests which have malformed sources:
|
||||
<ul>
|
||||
<li>
|
||||
MethodSource has unknown target or method is not static, no-arg
|
||||
</li>
|
||||
<li>
|
||||
ValueSource/EnumSource types are not convertible to method parameters
|
||||
</li>
|
||||
<li>
|
||||
No sources are defined
|
||||
</li>
|
||||
</ul>
|
||||
<!-- tooltip end -->
|
||||
<p>
|
||||
<small>New in 2017.2</small>
|
||||
</body>
|
||||
</html>
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.execution.junit.codeInsight;
|
||||
|
||||
import com.intellij.codeInspection.InspectionProfileEntry;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.siyeh.ig.LightInspectionTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class JUnit5MalformedParameterizedTest extends LightInspectionTestCase {
|
||||
@Nullable
|
||||
@Override
|
||||
protected InspectionProfileEntry getInspection() {
|
||||
return new JUnit5MalformedParameterizedInspection();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
addEnvironmentClass("package org.junit.jupiter.params;\n" +
|
||||
"public @interface ParameterizedTest {}");
|
||||
addEnvironmentClass("package org.junit.jupiter.params.provider;\n" +
|
||||
"public @interface MethodSource {String[] names();}");
|
||||
addEnvironmentClass("package org.junit.jupiter.params.provider;\n" +
|
||||
"public @interface EnumSource { Class<? extends Enum<?>> value();}");
|
||||
addEnvironmentClass("package org.junit.jupiter.params.provider;\n" +
|
||||
"public @interface ValueSource {\n" +
|
||||
"String[] strings() default {};\n" +
|
||||
"int[] ints() default {};\n" +
|
||||
"long[] longs() default {};\n" +
|
||||
"double[] doubles() default {};\n" +
|
||||
"}\n");
|
||||
}
|
||||
|
||||
public void testMalformedSources() throws Exception {
|
||||
doTest();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getBasePath() {
|
||||
return "/plugins/junit/testData/codeInsight/malformedParameterized";
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LightProjectDescriptor getProjectDescriptor() {
|
||||
return JAVA_8;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.*;
|
||||
|
||||
class ParameterizedTestsDemo {
|
||||
|
||||
<warning descr="No sources are provided, the suite would be empty">@ParameterizedTest</warning>
|
||||
void testWithParamsNoSource(int i) { }
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource(names = {<warning descr="Method source 'a' must be static">"a"</warning>,
|
||||
<warning descr="Method source 'b' should have no parameters">"b"</warning>,
|
||||
<warning descr="Method source 'c' must have one of the following return type: Stream<?>, Iterator<?>, Iterable<?> or Object[]">"c"</warning>,
|
||||
"d"})
|
||||
void testWithParams(Object s) { }
|
||||
|
||||
String[] a() {
|
||||
return new String[] {"a", "b"};
|
||||
}
|
||||
|
||||
static String[] b(int i) {
|
||||
return new String[] {"a", "b"};
|
||||
}
|
||||
|
||||
static Object c() {
|
||||
return new String[] {"a", "b"};
|
||||
}
|
||||
|
||||
static Object[] d() {
|
||||
return new String[] {"a", "b"};
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource(names = {<warning descr="Multiple parameters have to be wrapped in Arguments">"d"</warning>})
|
||||
void testWithMultipleParams(Object s, int i) { }
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(<warning descr="No implicit conversion found to convert object of type E to int">E.class</warning>)
|
||||
void testWithEnumSource(int i) { }
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(E.class)
|
||||
void testWithEnumSourceCorrect(E e) { }
|
||||
|
||||
enum E {
|
||||
A, B;
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {1})
|
||||
void testWithValues(int i) { }
|
||||
|
||||
@ParameterizedTest
|
||||
<warning descr="Exactly one type of input must be provided">@ValueSource(ints = {1},
|
||||
strings = <warning descr="No implicit conversion found to convert object of type String to int">"str"</warning>)</warning>
|
||||
void testWithMultipleValues(int i) { }
|
||||
|
||||
@ParameterizedTest
|
||||
<warning descr="No value source is defined">@ValueSource()</warning>
|
||||
void testWithNoValues(int i) { }
|
||||
|
||||
@ParameterizedTest
|
||||
<warning descr="Multiple parameters are not supported by this source">@ValueSource(ints = 1)</warning>
|
||||
void testWithValuesMultipleParams(int i, int j) { }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user