diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/TagsTestDataUtil.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/TagsTestDataUtil.java index aa7f79fe01f..cd38d1e8341 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/TagsTestDataUtil.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/TagsTestDataUtil.java @@ -21,28 +21,23 @@ import com.intellij.codeInsight.daemon.LineMarkerInfo; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.List; import java.util.stream.Collectors; public class TagsTestDataUtil { public static String insertInfoTags(List lineMarkers, boolean withDescription, String text) { - List lineMarkerPoints = Lists.newArrayList(); - for (LineMarkerInfo markerInfo : lineMarkers) { - lineMarkerPoints.add(new LineMarkerTagPoint(markerInfo.startOffset, true, markerInfo, withDescription)); - lineMarkerPoints.add(new LineMarkerTagPoint(markerInfo.endOffset, false, markerInfo, withDescription)); - } + List lineMarkerPoints = toLineMarkerTagPoints(lineMarkers, withDescription); return insertTagsInText(lineMarkerPoints, text); } public static String insertInfoTags(List highlights, String text) { - List highlightPoints = Lists.newArrayList(); - for (HighlightInfo highlight : highlights) { - highlightPoints.add(new HighlightTagPoint(highlight.startOffset, true, highlight)); - highlightPoints.add(new HighlightTagPoint(highlight.endOffset, false, highlight)); - } + List highlightPoints = toHighlightTagPoints(highlights); return insertTagsInText(highlightPoints, text); } @@ -88,6 +83,29 @@ public class TagsTestDataUtil { return builder.toString(); } + @NotNull + public static List toLineMarkerTagPoints( + Collection lineMarkers, + boolean withDescription + ) { + List lineMarkerPoints = Lists.newArrayList(); + for (LineMarkerInfo markerInfo : lineMarkers) { + lineMarkerPoints.add(new LineMarkerTagPoint(markerInfo.startOffset, true, markerInfo, withDescription)); + lineMarkerPoints.add(new LineMarkerTagPoint(markerInfo.endOffset, false, markerInfo, withDescription)); + } + return lineMarkerPoints; + } + + @NotNull + public static List toHighlightTagPoints(Collection highlights) { + List highlightPoints = Lists.newArrayList(); + for (HighlightInfo highlight : highlights) { + highlightPoints.add(new HighlightTagPoint(highlight.startOffset, true, highlight)); + highlightPoints.add(new HighlightTagPoint(highlight.endOffset, false, highlight)); + } + return highlightPoints; + } + public static class TagInfo implements Comparable> { protected final int offset; protected final boolean isStart; @@ -144,7 +162,7 @@ public class TagsTestDataUtil { } } - private static class HighlightTagPoint extends TagInfo { + public static class HighlightTagPoint extends TagInfo { private final HighlightInfo highlightInfo; private HighlightTagPoint(int offset, boolean start, HighlightInfo info) { @@ -165,9 +183,10 @@ public class TagsTestDataUtil { public String getAttributesString() { if (isStart) { if (highlightInfo.getDescription() != null) { - return String.format("textAttributesKey=\"%s\" descr=%s", - highlightInfo.forcedTextAttributesKey, - highlightInfo.getDescription()); + return String.format("descr=\"%s\" textAttributesKey=\"%s\"", + sanitizeLineBreaks(highlightInfo.getDescription()), + highlightInfo.forcedTextAttributesKey + ); } else { return String.format("textAttributesKey=\"%s\"", highlightInfo.forcedTextAttributesKey); @@ -179,7 +198,7 @@ public class TagsTestDataUtil { } } - private static class LineMarkerTagPoint extends TagInfo { + public static class LineMarkerTagPoint extends TagInfo { private final boolean withDescription; public LineMarkerTagPoint(int offset, boolean start, LineMarkerInfo info, boolean withDescription) { @@ -196,7 +215,17 @@ public class TagsTestDataUtil { @NotNull @Override public String getAttributesString() { - return withDescription ? String.format("descr=\"%s\"", data.getLineMarkerTooltip()) : "descr=\"*\""; + return withDescription ? String.format("descr=\"%s\"", sanitizeLineMarkerTooltip(data.getLineMarkerTooltip())) : "descr=\"*\""; } } + + private static @NotNull String sanitizeLineMarkerTooltip(@Nullable String originalText) { + if (originalText == null) return "null"; + String noHtmlTags = StringUtil.removeHtmlTags(originalText); + return sanitizeLineBreaks(noHtmlTags); + } + + private static String sanitizeLineBreaks(String originalText) { + return StringUtil.replace(originalText, "\n", " "); + } } diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/GradleMultiplatformHighlightingTest.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/GradleMultiplatformHighlightingTest.kt index bd4d238f5ec..fc043a849f4 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/GradleMultiplatformHighlightingTest.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/gradle/GradleMultiplatformHighlightingTest.kt @@ -8,19 +8,31 @@ package org.jetbrains.kotlin.gradle import com.intellij.codeInsight.EditorInfo import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl +import com.intellij.codeInsight.daemon.impl.HighlightInfo +import com.intellij.lang.annotation.HighlightSeverity import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.impl.EditorImpl import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.fileEditor.OpenFileDescriptor import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import com.intellij.testFramework.ExpectedHighlightingData +import com.intellij.testFramework.VfsTestUtil import com.intellij.testFramework.runInEdtAndWait +import junit.framework.TestCase import org.jetbrains.kotlin.idea.codeInsight.gradle.GradleImportingTestCase +import org.jetbrains.kotlin.test.KotlinTestUtils +import org.jetbrains.kotlin.test.TagsTestDataUtil +import org.jetbrains.kotlin.test.TagsTestDataUtil.TagInfo import org.jetbrains.plugins.gradle.tooling.annotation.TargetVersions import org.junit.Assert import org.junit.Test +import java.io.File class GradleMultiplatformHighlightingTest : GradleImportingTestCase() { @@ -62,9 +74,47 @@ abstract class GradleDaemonAnalyzerTestCase( fun checkHighlighting(project: Project, editor: Editor) { myProject = project runInEdtAndWait { + // This will prepare ExpectedHighlightingData, clear current editor from testing tags, and then + // call doCheckResult, which we override checkHighlighting(editor, checkWarnings, checkInfos) } } + + // We have to override doCheckResult because one from DaemonAnalyzerTestCase has few flaws: + // - overlapping line markers lead to crash in 193 (fixed in later versions) + // - it checks line markers and rest of highlighting *separately*, which means that we can't put both in + // expected test data + // - no API to sanitize error descriptions (in particular, we have to remove endlines in diagnostic messages, + // otherwise parser in testdata goes completely insane) + override fun doCheckResult(data: ExpectedHighlightingData, infos: MutableCollection, text: String) { + performGenericHighlightingAndLineMarkersChecks(infos, text) + performAdditionalChecksAfterHighlighting(editor) + } + + private fun performGenericHighlightingAndLineMarkersChecks(infos: Collection, text: String) { + val lineMarkersTags: List> = if (testLineMarkers) { + TagsTestDataUtil.toLineMarkerTagPoints( + DaemonCodeAnalyzerImpl.getLineMarkers(getDocument(file), project), + /* withDescription = */ true + ) + } else { + emptyList() + } + + val filteredHighlights = infos.filterNot { + it.severity == HighlightSeverity.INFORMATION && !checkInfos || + it.severity == HighlightSeverity.WARNING && !checkWarnings + } + val highlightsTags: List> = TagsTestDataUtil.toHighlightTagPoints(filteredHighlights) + + val allTags = lineMarkersTags + highlightsTags + val actualTextWithTags = TagsTestDataUtil.insertTagsInText(allTags, text) + + val physicalFileWithExpectedTestData = file.testDataFileByUserData + KotlinTestUtils.assertEqualsToFile(physicalFileWithExpectedTestData, actualTextWithTags) + } + + protected open fun performAdditionalChecksAfterHighlighting(editor: Editor) { } } internal fun checkFiles( @@ -73,17 +123,21 @@ internal fun checkFiles( analyzer: GradleDaemonAnalyzerTestCase ) { var atLeastOneFile = false - val content = mutableMapOf() - files.forEach { file -> - val (_, textWithTags) = configureEditorByExistingFile(file, project) + + // We have to first load all files into the project and only then start + // highlighting, otherwise cross-file references might not work + val editors = files.map { file -> atLeastOneFile = true - content[file] = textWithTags + + val originalText= VfsUtil.loadText(file) + val textWithoutTags = textWithoutTags(originalText) + + configureEditorByExistingFile(file, project, textWithoutTags) } + + editors.forEach { analyzer.checkHighlighting(project, it) } + Assert.assertTrue(atLeastOneFile) - files.forEach { file -> - val (editor, _) = configureEditorByExistingFile(file, project, content[file]) - analyzer.checkHighlighting(project, editor) - } } internal fun textWithoutTags(text: String): String { @@ -104,24 +158,43 @@ private fun createEditor(file: VirtualFile, project: Project): Editor { internal fun configureEditorByExistingFile( virtualFile: VirtualFile, project: Project, - contentToSet: String? = null -): Pair { - var result: Pair? = null + contentToSet: String +): Editor { + var result: Editor? = null runInEdtAndWait { val editor = createEditor(virtualFile, project) val document = editor.document val editorInfo = EditorInfo(document.text) - val textWithTags = editorInfo.newFileText ApplicationManager.getApplication().runWriteAction { - val newText = contentToSet ?: textWithoutTags(textWithTags) - if (document.text != newText) { - document.setText(newText) + if (document.text != contentToSet) { + document.setText(contentToSet) } editorInfo.applyToEditor(editor) } PsiDocumentManager.getInstance(project).commitAllDocuments() - result = editor to textWithTags + result = editor } return result!! -} \ No newline at end of file +} + +/** + * Returns original physical file with expected test data, i.e. something like 'kotlin/idea/testData/gradle/runConfigurations/myTest/Foo.kt' + * + * Note that it's different from physical file used in project during test run (which is usually a copy in temporary folder) + * + * All inheritors of GradleImportingTestCase should normally have PsiFiles with properly attached UserData to them + * (see [GradleImportingTestCase.configureByFiles]) + */ +internal val VirtualFile.testDataFileByUserData: File + get() { + val physicalFileWithExpectedTestData = File(this.getUserData(VfsTestUtil.TEST_DATA_FILE_PATH)!!) + TestCase.assertTrue( + "Can't find file with expected test data by absolute path ${physicalFileWithExpectedTestData.absolutePath}", + physicalFileWithExpectedTestData.exists() + ) + return physicalFileWithExpectedTestData + } + +internal val PsiFile.testDataFileByUserData: File + get() = virtualFile.testDataFileByUserData \ No newline at end of file diff --git a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleImportingTestCase.kt b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleImportingTestCase.kt index 972662e1981..6a185472651 100644 --- a/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleImportingTestCase.kt +++ b/idea/idea-gradle/tests/org/jetbrains/kotlin/idea/codeInsight/gradle/GradleImportingTestCase.kt @@ -35,6 +35,7 @@ import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VirtualFile import com.intellij.testFramework.IdeaTestUtil +import com.intellij.testFramework.VfsTestUtil import com.intellij.util.PathUtil import com.intellij.util.containers.ContainerUtil import junit.framework.TestCase @@ -243,13 +244,21 @@ abstract class GradleImportingTestCase : ExternalSystemImportingTestCase() { return rootDir.walk().mapNotNull { when { it.isDirectory -> null + !it.name.endsWith(SUFFIX) -> { var text = it.readText() (properties ?: mapOf("kotlin_plugin_version" to LATEST_STABLE_GRADLE_PLUGIN_VERSION)).forEach { key, value -> text = text.replace("{{${key}}}", value) } - createProjectSubFile(it.path.substringAfter(rootDir.path + File.separator), text) + val virtualFile = createProjectSubFile(it.path.substringAfter(rootDir.path + File.separator), text) + + // Real file with expected testdata allows to throw nicer exceptions in + // case of mismatch, as well as open interactive diff window in IDEA + virtualFile.putUserData(VfsTestUtil.TEST_DATA_FILE_PATH, it.absolutePath) + + virtualFile } + else -> null } }.toList() diff --git a/idea/testData/gradle/importAndCheckHighlighting/multiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt b/idea/testData/gradle/importAndCheckHighlighting/multiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt index b047d192f93..853f893b8c9 100644 --- a/idea/testData/gradle/importAndCheckHighlighting/multiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt +++ b/idea/testData/gradle/importAndCheckHighlighting/multiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt @@ -1,7 +1,7 @@ package sample expect class Sample() { - fun checkMe(): Int + fun checkMe(): Int } expect object Platform { diff --git a/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt b/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt index ca79b5bb60d..3a8718e2512 100644 --- a/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt +++ b/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/commonMain/kotlin/sample/Sample.kt @@ -1,4 +1,4 @@ package sample fun common(): Boolean { - return "" + return "" } \ No newline at end of file diff --git a/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/jvmMain/kotlin/sample/SampleJvm.kt b/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/jvmMain/kotlin/sample/SampleJvm.kt index ff0d16d6566..8b9b5ac59e5 100644 --- a/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/jvmMain/kotlin/sample/SampleJvm.kt +++ b/idea/testData/gradle/importAndCheckHighlighting/unresolvedInMultiplatformLibrary/src/jvmMain/kotlin/sample/SampleJvm.kt @@ -2,5 +2,5 @@ package sample fun jvm() { println(common()) - println(js()) + println(js()) } \ No newline at end of file diff --git a/idea/testData/gradle/newMultiplatformHighlighting/first/src/commonMain/kotlin/My.kt b/idea/testData/gradle/newMultiplatformHighlighting/first/src/commonMain/kotlin/My.kt index 3105c058a07..cb6e6c66374 100644 --- a/idea/testData/gradle/newMultiplatformHighlighting/first/src/commonMain/kotlin/My.kt +++ b/idea/testData/gradle/newMultiplatformHighlighting/first/src/commonMain/kotlin/My.kt @@ -1,3 +1,3 @@ -expect class My { - fun foo() +expect class My { + fun foo() } \ No newline at end of file diff --git a/idea/testData/gradle/newMultiplatformHighlighting/first/src/jvmMain/kotlin/My.kt b/idea/testData/gradle/newMultiplatformHighlighting/first/src/jvmMain/kotlin/My.kt index ec1ab12b76a..4d18984a57f 100644 --- a/idea/testData/gradle/newMultiplatformHighlighting/first/src/jvmMain/kotlin/My.kt +++ b/idea/testData/gradle/newMultiplatformHighlighting/first/src/jvmMain/kotlin/My.kt @@ -1,5 +1,5 @@ -actual class My { - actual fun foo() {} +actual class My { + actual fun foo() {} - actual fun bar() {} + actual fun bar() {} } \ No newline at end of file diff --git a/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/commonMain/kotlin/My.kt b/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/commonMain/kotlin/My.kt index d32be60205b..fcd89975da6 100644 --- a/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/commonMain/kotlin/My.kt +++ b/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/commonMain/kotlin/My.kt @@ -1,3 +1,3 @@ -expect class My { - fun foo() +expect class My { + fun foo() } \ No newline at end of file diff --git a/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jsMain/kotlin/My.kt b/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jsMain/kotlin/My.kt index bad18cafd01..f4aadedb56b 100644 --- a/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jsMain/kotlin/My.kt +++ b/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jsMain/kotlin/My.kt @@ -1,3 +1,3 @@ -actual class My { - actual fun foo() {} +actual class My { + actual fun foo() {} } \ No newline at end of file diff --git a/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jvmMain/kotlin/My.kt b/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jvmMain/kotlin/My.kt index bad18cafd01..f4aadedb56b 100644 --- a/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jvmMain/kotlin/My.kt +++ b/idea/testData/gradle/newMultiplatformHighlighting/noErrors/src/jvmMain/kotlin/My.kt @@ -1,3 +1,3 @@ -actual class My { - actual fun foo() {} +actual class My { + actual fun foo() {} } \ No newline at end of file diff --git a/idea/testData/multiModuleLineMarker/kotlinTestAnnotations/a_js_test_dep(kotlin-test)/test.kt b/idea/testData/multiModuleLineMarker/kotlinTestAnnotations/a_js_test_dep(kotlin-test)/test.kt index 74ef7dd477d..9ef8f426ebd 100644 --- a/idea/testData/multiModuleLineMarker/kotlinTestAnnotations/a_js_test_dep(kotlin-test)/test.kt +++ b/idea/testData/multiModuleLineMarker/kotlinTestAnnotations/a_js_test_dep(kotlin-test)/test.kt @@ -1,7 +1,7 @@ import kotlin.test.* -class SimpleTest { - @Test fun testFoo() { +class SimpleTest { + @Test fun testFoo() { // Will run }