Test local inspections on the fly via highlighting
Fixes problem with INFORMATION highlight type in batch mode (IDEA 181). It's no more required to report INFORMATIONs in unit tests in batch.
This commit is contained in:
+1
-2
@@ -66,8 +66,7 @@ abstract class AbstractKotlinInspection: LocalInspectionTool(), CustomSuppressab
|
||||
range: TextRange?,
|
||||
vararg fixes: LocalQuickFix
|
||||
) {
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode &&
|
||||
!isOnTheFly && highlightType == ProblemHighlightType.INFORMATION) return
|
||||
if (!isOnTheFly && highlightType == ProblemHighlightType.INFORMATION) return
|
||||
val problemDescriptor = manager.createProblemDescriptor(element, range, description, highlightType, isOnTheFly, *fixes)
|
||||
registerProblem(problemDescriptor)
|
||||
}
|
||||
|
||||
@@ -31,21 +31,5 @@
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis for nullable boolean check</problem_class>
|
||||
<description>Equality check should be used instead of elvis for nullable boolean check</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>13</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="test.kt"/>
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis for nullable boolean check</problem_class>
|
||||
<description>Equality check can be used instead of elvis for nullable boolean check</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>14</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="test.kt"/>
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Equality check can be used instead of elvis for nullable boolean check</problem_class>
|
||||
<description>Equality check can be used instead of elvis for nullable boolean check</description>
|
||||
</problem>
|
||||
|
||||
</problems>
|
||||
+2
-2
@@ -10,6 +10,6 @@ fun foo() {
|
||||
if (a ?: false || !(b ?: true)) {
|
||||
|
||||
}
|
||||
val x = a ?: false
|
||||
val y = !(b ?: true)
|
||||
val x = a ?: false // INFORMATION -- not reported in batch
|
||||
val y = !(b ?: true) // INFORMATION -- not reported in batch
|
||||
}
|
||||
-16
@@ -31,20 +31,4 @@
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can be replaced with binary operator</problem_class>
|
||||
<description>Call replaceable with binary operator</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>8</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can be replaced with binary operator</problem_class>
|
||||
<description>Call replaceable with binary operator</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>test.kt</file>
|
||||
<line>9</line>
|
||||
<module>light_idea_test_case</module>
|
||||
<entry_point TYPE="file" FQNAME="temp:///src/test.kt" />
|
||||
<problem_class severity="WARNING" attribute_key="WARNING_ATTRIBUTES">Can be replaced with binary operator</problem_class>
|
||||
<description>Call replaceable with binary operator</description>
|
||||
</problem>
|
||||
</problems>
|
||||
@@ -5,6 +5,6 @@ fun foo() {
|
||||
1.compareTo(1) == 0 // NO
|
||||
2.compareTo(1) > 0 // YES
|
||||
0 >= 1.compareTo(2) // YES
|
||||
2.plus(2) // YES (information)
|
||||
2.times(2) // YES (information)
|
||||
2.plus(2) // YES (information) -- not reported in batch
|
||||
2.times(2) // YES (information) -- not reported in batch
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
// HIGHLIGHT: INFORMATION
|
||||
|
||||
fun foo() {
|
||||
var a: Boolean? = null
|
||||
val x = a<caret> ?: false
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// HIGHLIGHT: INFORMATION
|
||||
|
||||
fun foo() {
|
||||
var a: Boolean? = null
|
||||
val x = a == true
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// HIGHLIGHT: INFORMATION
|
||||
|
||||
fun foo() {
|
||||
var b: Boolean? = null
|
||||
val x = !(b <caret>?: true)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// HIGHLIGHT: INFORMATION
|
||||
|
||||
fun foo() {
|
||||
var b: Boolean? = null
|
||||
val x = b == false
|
||||
|
||||
@@ -17,11 +17,15 @@
|
||||
package org.jetbrains.kotlin.idea.inspections
|
||||
|
||||
import com.google.common.collect.Lists
|
||||
import com.intellij.codeInspection.ProblemDescriptor
|
||||
import com.intellij.codeHighlighting.HighlightDisplayLevel
|
||||
import com.intellij.codeHighlighting.Pass
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfoType
|
||||
import com.intellij.codeInsight.intention.EmptyIntentionAction
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager
|
||||
import com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl
|
||||
import junit.framework.ComparisonFailure
|
||||
import junit.framework.TestCase
|
||||
import org.jetbrains.kotlin.idea.test.DirectiveBasedActionUtils
|
||||
@@ -29,7 +33,6 @@ import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.configureCompilerOptions
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||
import org.jetbrains.kotlin.test.KotlinTestUtils
|
||||
import org.junit.Assert
|
||||
@@ -64,12 +67,16 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
|
||||
}
|
||||
|
||||
if (candidateFiles.isEmpty()) {
|
||||
throw AssertionError(".inspection file is not found for " + testDataFile +
|
||||
"\nAdd it to base directory of test data. It should contain fully-qualified name of inspection class.")
|
||||
throw AssertionError(
|
||||
".inspection file is not found for " + testDataFile +
|
||||
"\nAdd it to base directory of test data. It should contain fully-qualified name of inspection class."
|
||||
)
|
||||
}
|
||||
if (candidateFiles.size > 1) {
|
||||
throw AssertionError("Several .inspection files are available for " + testDataFile +
|
||||
"\nPlease remove some of them\n" + candidateFiles)
|
||||
throw AssertionError(
|
||||
"Several .inspection files are available for " + testDataFile +
|
||||
"\nPlease remove some of them\n" + candidateFiles
|
||||
)
|
||||
}
|
||||
|
||||
val className = FileUtil.loadFile(candidateFiles[0]).trim { it <= ' ' }
|
||||
@@ -106,9 +113,9 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
|
||||
break
|
||||
}
|
||||
|
||||
val psiFile = myFixture.configureByFiles(*(listOf(mainFile.name) + extraFileNames).toTypedArray()).first()
|
||||
myFixture.configureByFiles(*(listOf(mainFile.name) + extraFileNames).toTypedArray()).first()
|
||||
|
||||
doTestFor(mainFile.name, psiFile.virtualFile!!, inspection, fileText)
|
||||
doTestFor(mainFile.name, inspection, fileText)
|
||||
|
||||
if (file is KtFile && !InTextDirectivesUtils.isDirectiveDefined(fileText, "// SKIP_ERRORS_AFTER")) {
|
||||
DirectiveBasedActionUtils.checkForUnexpectedErrors(file as KtFile)
|
||||
@@ -116,90 +123,118 @@ abstract class AbstractLocalInspectionTest : KotlinLightCodeInsightFixtureTestCa
|
||||
}
|
||||
|
||||
protected fun runInspectionWithFixesAndCheck(
|
||||
file: VirtualFile,
|
||||
inspection: AbstractKotlinInspection,
|
||||
problemExpectedString: String?,
|
||||
highlightExpectedString: String?,
|
||||
localFixTextString: String?
|
||||
inspection: AbstractKotlinInspection,
|
||||
expectedProblemString: String?,
|
||||
expectedHighlightString: String?,
|
||||
localFixTextString: String?
|
||||
): Boolean {
|
||||
val problemExpected = problemExpectedString == null || problemExpectedString != "none"
|
||||
val presentation = runInspection(inspection, project, listOf(file))
|
||||
val problemDescriptors = presentation.problemDescriptors
|
||||
.filterIsInstance<ProblemDescriptor>()
|
||||
.filter {
|
||||
val caretOffset = myFixture.caretOffset
|
||||
caretOffset in it.textRangeInElement?.shiftRight(it.psiElement.startOffset) ?: it.psiElement.textRange
|
||||
}
|
||||
val problemExpected = expectedProblemString == null || expectedProblemString != "none"
|
||||
myFixture.enableInspections(inspection::class.java)
|
||||
|
||||
// Set default level to WARNING to make possible to test DO_NOT_SHOW
|
||||
val inspectionProfileManager = ProjectInspectionProfileManager.getInstance(project)
|
||||
val inspectionProfile = inspectionProfileManager.currentProfile
|
||||
val state = inspectionProfile.getToolDefaultState(inspection.shortName, project)
|
||||
state.level = HighlightDisplayLevel.WARNING
|
||||
|
||||
val caretOffset = myFixture.caretOffset
|
||||
val highlightInfos = CodeInsightTestFixtureImpl.instantiateAndRun(
|
||||
file, editor, intArrayOf(
|
||||
Pass.LINE_MARKERS,
|
||||
Pass.EXTERNAL_TOOLS,
|
||||
Pass.POPUP_HINTS,
|
||||
Pass.UPDATE_ALL,
|
||||
Pass.UPDATE_FOLDING,
|
||||
Pass.WOLF
|
||||
), false
|
||||
).filter { it.description != null && caretOffset in it.startOffset..it.endOffset }
|
||||
|
||||
Assert.assertTrue(
|
||||
if (!problemExpected)
|
||||
"No problems should be detected at caret\n" +
|
||||
"Detected problems: ${problemDescriptors.joinToString { it.descriptionTemplate }}"
|
||||
else
|
||||
"Expected at least one problem at caret",
|
||||
problemExpected == problemDescriptors.isNotEmpty())
|
||||
if (!problemExpected) return false
|
||||
problemDescriptors
|
||||
.filter { it.highlightType != ProblemHighlightType.INFORMATION }
|
||||
.forEach {
|
||||
Assert.assertTrue("Problem description should not contain 'can': ${it.descriptionTemplate}",
|
||||
" can " !in it.descriptionTemplate)
|
||||
}
|
||||
if (problemExpectedString != null) {
|
||||
Assert.assertTrue("Expected the following problem at caret: $problemExpectedString\n" +
|
||||
"Active problems: ${problemDescriptors.joinToString { it.descriptionTemplate }}",
|
||||
problemDescriptors.any { it.descriptionTemplate == problemExpectedString })
|
||||
if (!problemExpected)
|
||||
"No problems should be detected at caret\n" +
|
||||
"Detected problems: ${highlightInfos.joinToString { it.description }}"
|
||||
else
|
||||
"Expected at least one problem at caret",
|
||||
problemExpected == highlightInfos.isNotEmpty()
|
||||
)
|
||||
if (!problemExpected || highlightInfos.isEmpty()) return false
|
||||
highlightInfos
|
||||
.filter { it.type != HighlightInfoType.INFORMATION }
|
||||
.forEach {
|
||||
val description = it.description
|
||||
Assert.assertTrue(
|
||||
"Problem description should not contain 'can': $description",
|
||||
" can " !in description
|
||||
)
|
||||
}
|
||||
|
||||
if (expectedProblemString != null) {
|
||||
Assert.assertTrue(
|
||||
"Expected the following problem at caret: $expectedProblemString\n" +
|
||||
"Active problems: ${highlightInfos.joinToString { it.description }}",
|
||||
highlightInfos.any { it.description == expectedProblemString }
|
||||
)
|
||||
}
|
||||
if (highlightExpectedString != null) {
|
||||
Assert.assertTrue("Expected the following problem highlight type\n" +
|
||||
"Actual types: ${problemDescriptors.joinToString { it.highlightType.toString() } }",
|
||||
problemDescriptors.all { it.highlightType.toString() == highlightExpectedString })
|
||||
val expectedHighlightType = when (expectedHighlightString) {
|
||||
null -> null
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING.name -> HighlightDisplayLevel.WARNING.name
|
||||
else -> expectedHighlightString
|
||||
}
|
||||
if (expectedHighlightType != null) {
|
||||
Assert.assertTrue(
|
||||
"Expected the following problem highlight type: $expectedHighlightType\n" +
|
||||
"Actual type: ${highlightInfos.joinToString { it.type.toString() }}",
|
||||
highlightInfos.all { expectedHighlightType in it.type.toString() }
|
||||
)
|
||||
}
|
||||
|
||||
val allLocalFixActions = problemDescriptors.flatMap { problem ->
|
||||
val fixes = problem.fixes
|
||||
fixes?.toList() ?: emptyList()
|
||||
}
|
||||
val localFixActions = allLocalFixActions.filter { fix -> localFixTextString == null || fix.name == localFixTextString }
|
||||
val availableDescription = allLocalFixActions.joinToString { it.name }
|
||||
val allLocalFixActions = highlightInfos.flatMap { it.quickFixActionMarkers }.map { it.first.action }
|
||||
|
||||
val fixDescription = localFixTextString?.let { "with specified text '$localFixTextString'"} ?: ""
|
||||
TestCase.assertTrue("No fix action $fixDescription\n" +
|
||||
"Available actions: $availableDescription",
|
||||
localFixActions.isNotEmpty())
|
||||
val localFixActions = allLocalFixActions.filter { fix -> localFixTextString == null || fix.text == localFixTextString }
|
||||
val availableDescription = allLocalFixActions.joinToString { it.text }
|
||||
|
||||
val localFixAction = localFixActions.singleOrNull()
|
||||
TestCase.assertTrue("More than one fix action $fixDescription\n" +
|
||||
"Available actions: $availableDescription",
|
||||
localFixAction != null)
|
||||
val fixDescription = localFixTextString?.let { "with specified text '$localFixTextString'" } ?: ""
|
||||
TestCase.assertTrue(
|
||||
"No fix action $fixDescription\n" +
|
||||
"Available actions: $availableDescription",
|
||||
localFixActions.isNotEmpty()
|
||||
)
|
||||
|
||||
val problemDescriptor = problemDescriptors.find { localFixAction in it.fixes?.toList() ?: emptyList() }!!
|
||||
val localFixAction = localFixActions.singleOrNull { it !is EmptyIntentionAction }
|
||||
TestCase.assertTrue(
|
||||
"More than one fix action $fixDescription\n" +
|
||||
"Available actions: $availableDescription",
|
||||
localFixAction != null
|
||||
)
|
||||
|
||||
project.executeWriteCommand(localFixAction!!.name, null) {
|
||||
localFixAction.applyFix(project, problemDescriptor)
|
||||
project.executeWriteCommand(localFixAction!!.text, null) {
|
||||
localFixAction.invoke(project, editor, file)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun doTestFor(mainFilePath: String, file: VirtualFile, inspection: AbstractKotlinInspection, fileText: String) {
|
||||
val problemExpectedString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||
fileText, "// $expectedProblemDirectiveName: ")
|
||||
val highlightExpectedString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||
fileText, "// $expectedProblemHighlightType: ")
|
||||
private fun doTestFor(mainFilePath: String, inspection: AbstractKotlinInspection, fileText: String) {
|
||||
val expectedProblemString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||
fileText, "// $expectedProblemDirectiveName: "
|
||||
)
|
||||
val expectedHighlightString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||
fileText, "// $expectedProblemHighlightType: "
|
||||
)
|
||||
val localFixTextString = InTextDirectivesUtils.findStringWithPrefixes(
|
||||
fileText, "// $fixTextDirectiveName: ")
|
||||
fileText, "// $fixTextDirectiveName: "
|
||||
)
|
||||
|
||||
if (!runInspectionWithFixesAndCheck(file, inspection, problemExpectedString, highlightExpectedString, localFixTextString)) {
|
||||
if (!runInspectionWithFixesAndCheck(inspection, expectedProblemString, expectedHighlightString, localFixTextString)) {
|
||||
return
|
||||
}
|
||||
|
||||
val canonicalPathToExpectedFile = mainFilePath + afterFileNameSuffix
|
||||
try {
|
||||
myFixture.checkResultByFile(canonicalPathToExpectedFile)
|
||||
}
|
||||
catch (e: ComparisonFailure) {
|
||||
} catch (e: ComparisonFailure) {
|
||||
KotlinTestUtils.assertEqualsToFile(
|
||||
File(testDataPath, canonicalPathToExpectedFile),
|
||||
editor.document.text
|
||||
File(testDataPath, canonicalPathToExpectedFile),
|
||||
editor.document.text
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -58,10 +58,10 @@ abstract class AbstractMultiFileLocalInspectionTest : AbstractLocalInspectionTes
|
||||
val problemExpectedString = config["problem"]?.asString // null means "some problem", "none" means no problem
|
||||
val localFixTextString = config["fix"]?.asString // null means "some single fix" or "none" if no problem expected
|
||||
|
||||
doTest(path) test@ { _ ->
|
||||
val mainFile = myFixture.configureFromTempProjectFile(mainFilePath)
|
||||
doTest(path) test@{ _ ->
|
||||
myFixture.configureFromTempProjectFile(mainFilePath)
|
||||
|
||||
runInspectionWithFixesAndCheck(mainFile.virtualFile, inspection, problemExpectedString, null, localFixTextString)
|
||||
runInspectionWithFixesAndCheck(inspection, problemExpectedString, null, localFixTextString)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user