Skip to content

Commit ae9bd37

Browse files
committed
Unit Test Tooling: Test navigation
#KT-6472 In Progress
1 parent ad929a6 commit ae9bd37

18 files changed

+346
-3
lines changed

generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.generators.tests.generator.TestGenerator.TargetBacke
4141
import org.jetbrains.kotlin.generators.tests.reservedWords.generateTestDataForReservedWords
4242
import org.jetbrains.kotlin.idea.AbstractExpressionSelectionTest
4343
import org.jetbrains.kotlin.idea.AbstractSmartSelectionTest
44+
import org.jetbrains.kotlin.idea.actions.AbstractGotoTestOrCodeActionTest
4445
import org.jetbrains.kotlin.idea.codeInsight.*
4546
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateActionTest
4647
import org.jetbrains.kotlin.idea.codeInsight.moveUpDown.AbstractCodeMoverTest
@@ -411,6 +412,10 @@ fun main(args: Array<String>) {
411412
model("navigation/implementations", recursive = false)
412413
}
413414

415+
testClass<AbstractGotoTestOrCodeActionTest>() {
416+
model("navigation/gotoTestOrCode", pattern = "^(.+)\\.main\\..+\$")
417+
}
418+
414419
testClass<AbstractQuickFixMultiFileTest>() {
415420
model("quickfix", pattern = """^(\w+)\.before\.Main\.\w+$""", testMethod = "doTestWithExtraFile")
416421
}

idea/src/META-INF/plugin.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,9 @@
612612

613613
<codeInsight.typeInfo language="kotlin" implementationClass="org.jetbrains.kotlin.idea.codeInsight.KotlinExpressionTypeProvider"/>
614614

615+
<testCreator language="kotlin" implementationClass="org.jetbrains.kotlin.idea.testIntegration.KotlinTestCreator"/>
616+
<testFinder implementation="org.jetbrains.kotlin.idea.testIntegration.KotlinTestFinder"/>
617+
615618
<intentionAction>
616619
<className>org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention</className>
617620
<category>Kotlin</category>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2010-2015 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.testIntegration
18+
19+
import com.intellij.openapi.editor.Editor
20+
import com.intellij.openapi.project.Project
21+
import com.intellij.psi.PsiFile
22+
import com.intellij.testIntegration.TestCreator
23+
24+
class KotlinTestCreator : TestCreator {
25+
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean {
26+
return KotlinCreateTestIntention().isAvailable(project, editor, file)
27+
}
28+
29+
override fun createTest(project: Project, editor: Editor, file: PsiFile) {
30+
KotlinCreateTestIntention().invoke(project, editor, file)
31+
}
32+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2010-2015 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.testIntegration
18+
19+
import com.intellij.codeInsight.TestFrameworks
20+
import com.intellij.openapi.util.Pair
21+
import com.intellij.openapi.util.text.StringUtil
22+
import com.intellij.psi.PsiClass
23+
import com.intellij.psi.PsiElement
24+
import com.intellij.psi.PsiNamedElement
25+
import com.intellij.psi.search.PsiShortNamesCache
26+
import com.intellij.testIntegration.JavaTestFinder
27+
import com.intellij.testIntegration.TestFinderHelper
28+
import com.intellij.util.CommonProcessors
29+
import com.intellij.util.containers.HashSet
30+
import org.jetbrains.kotlin.asJava.KotlinLightClassForExplicitDeclaration
31+
import org.jetbrains.kotlin.asJava.toLightClass
32+
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
33+
import org.jetbrains.kotlin.psi.JetClassOrObject
34+
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
35+
import java.util.*
36+
import java.util.regex.Pattern
37+
38+
// Based on com.intellij.testIntegration.JavaTestFinder.JavaTestFinder implementation
39+
// TODO: We can reuse JavaTestFinder if Kotlin classes have their isPhysical() return true
40+
class KotlinTestFinder : JavaTestFinder() {
41+
override fun findSourceElement(from: PsiElement): PsiClass? {
42+
super.findSourceElement(from)?.let { return it }
43+
44+
val classOrObject = from.parentsWithSelf.filterIsInstance<JetClassOrObject>().firstOrNull { !it.isLocal() } ?: return null
45+
if (classOrObject.resolveToDescriptorIfAny() == null) return null
46+
return classOrObject.toLightClass()
47+
}
48+
49+
override fun isTest(element: PsiElement): Boolean {
50+
val sourceElement = findSourceElement(element) ?: return false
51+
return super.isTest(sourceElement)
52+
}
53+
54+
override fun findClassesForTest(element: PsiElement): Collection<PsiElement> {
55+
val klass = findSourceElement(element) ?: return emptySet()
56+
57+
val scope = getSearchScope(element, true)
58+
59+
val cache = PsiShortNamesCache.getInstance(element.project)
60+
61+
val frameworks = TestFrameworks.getInstance()
62+
val classesWithWeights = ArrayList<Pair<out PsiNamedElement, Int>>()
63+
for (candidateNameWithWeight in TestFinderHelper.collectPossibleClassNamesWithWeights(klass.name)) {
64+
for (eachClass in cache.getClassesByName(candidateNameWithWeight.first, scope)) {
65+
if (eachClass.isAnnotationType || frameworks.isTestClass(eachClass)) continue
66+
if (!eachClass.isPhysical && eachClass !is KotlinLightClassForExplicitDeclaration) continue
67+
68+
classesWithWeights.add(Pair.create(eachClass, candidateNameWithWeight.second))
69+
}
70+
}
71+
72+
return TestFinderHelper.getSortedElements(classesWithWeights, false)
73+
}
74+
75+
override fun findTestsForClass(element: PsiElement): Collection<PsiElement> {
76+
val klass = findSourceElement(element) ?: return emptySet()
77+
78+
val classesWithProximities = ArrayList<Pair<out PsiNamedElement, Int>>()
79+
val processor = CommonProcessors.CollectProcessor(classesWithProximities)
80+
81+
val klassName = klass.name!!
82+
val pattern = Pattern.compile(".*" + StringUtil.escapeToRegexp(klassName) + ".*", Pattern.CASE_INSENSITIVE)
83+
84+
val scope = getSearchScope(klass, false)
85+
val frameworks = TestFrameworks.getInstance()
86+
87+
val cache = PsiShortNamesCache.getInstance(klass.project)
88+
val names = HashSet<String>()
89+
cache.getAllClassNames(names)
90+
91+
for (candidateName in names) {
92+
if (!pattern.matcher(candidateName).matches()) continue
93+
for (candidateClass in cache.getClassesByName(candidateName, scope)) {
94+
if (!(frameworks.isTestClass(candidateClass) || frameworks.isPotentialTestClass(candidateClass))) continue
95+
if (!candidateClass.isPhysical && candidateClass !is KotlinLightClassForExplicitDeclaration) continue
96+
processor.process(Pair.create(candidateClass, TestFinderHelper.calcTestNameProximity(klassName, candidateName)))
97+
}
98+
}
99+
100+
return TestFinderHelper.getSortedElements(classesWithProximities, true)
101+
}
102+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import junit.framework.TestCase
2+
3+
class FooTest2 : TestCase()
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
2+
// REF: (<root>).FooTest2
3+
// REF: FooTest
4+
import junit.framework.TestCase;
5+
6+
public class <caret>Foo {
7+
8+
}
9+
10+
public class FooTest extends TestCase {
11+
12+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import junit.framework.TestCase
2+
3+
class Foo
4+
5+
class FooTest : TestCase()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
2+
// REF: (<root>).Foo
3+
import junit.framework.TestCase;
4+
5+
public class <caret>FooTest extends TestCase {
6+
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import junit.framework.TestCase;
2+
3+
public class FooTest2 extends TestCase {
4+
5+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
2+
// REF: (<root>).FooTest
3+
// REF: FooTest2
4+
import junit.framework.TestCase
5+
6+
class <caret>Foo
7+
8+
class FooTest : TestCase()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import junit.framework.TestCase;
2+
3+
public class Foo {
4+
5+
}
6+
7+
public class FooTest2 extends TestCase {
8+
9+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
2+
// REF: Foo
3+
import junit.framework.TestCase
4+
5+
class <caret>FooTest2 : TestCase()
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
2+
// REF: (<root>).Foo
3+
import junit.framework.TestCase
4+
5+
class Foo
6+
7+
class <caret>FooTest : TestCase()
8+
9+
class FooTest2 : TestCase()
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 2010-2015 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.actions
18+
19+
import com.intellij.openapi.editor.Editor
20+
import com.intellij.openapi.util.io.FileUtil
21+
import com.intellij.psi.PsiFile
22+
import com.intellij.testFramework.PlatformTestUtil
23+
import com.intellij.testIntegration.GotoTestOrCodeHandler
24+
import org.jetbrains.kotlin.idea.navigation.NavigationTestUtils
25+
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
26+
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
27+
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor
28+
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
29+
import org.jetbrains.kotlin.test.InTextDirectivesUtils
30+
import org.jetbrains.kotlin.test.JetTestUtils
31+
import java.io.File
32+
33+
abstract class AbstractGotoTestOrCodeActionTest : JetLightCodeInsightFixtureTestCase() {
34+
private object Handler: GotoTestOrCodeHandler() {
35+
public override fun getSourceAndTargetElements(editor: Editor?, file: PsiFile?) = super.getSourceAndTargetElements(editor, file)
36+
}
37+
38+
override fun getProjectDescriptor() = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
39+
40+
protected fun doTest(path: String) {
41+
val mainFile = File(path)
42+
val fileText = FileUtil.loadFile(mainFile, true)
43+
val addKotlinRuntime = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// WITH_RUNTIME") != null
44+
45+
try {
46+
if (addKotlinRuntime) {
47+
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
48+
}
49+
ConfigLibraryUtil.configureLibrariesByDirective(myModule, PlatformTestUtil.getCommunityPath(), fileText)
50+
51+
myFixture.testDataPath = "${JetTestUtils.getHomeDirectory()}/${mainFile.getParent()}"
52+
53+
val mainFileName = mainFile.name
54+
val mainFileBaseName = mainFileName.substring(0, mainFileName.indexOf('.'))
55+
mainFile.parentFile
56+
.listFiles { file, name ->
57+
name != mainFileName && name.startsWith("$mainFileBaseName.") && (name.endsWith(".kt") || name.endsWith(".java"))
58+
}
59+
.forEach{ myFixture.configureByFile(it.name) }
60+
val file = myFixture.configureByFile(mainFileName)
61+
62+
NavigationTestUtils.assertGotoDataMatching(editor, Handler.getSourceAndTargetElements(editor, file))
63+
}
64+
finally {
65+
ConfigLibraryUtil.unconfigureLibrariesByDirective(myModule, fileText)
66+
if (addKotlinRuntime) {
67+
ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
68+
}
69+
}
70+
}
71+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright 2010-2015 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jetbrains.kotlin.idea.actions;
18+
19+
import com.intellij.testFramework.TestDataPath;
20+
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
21+
import org.jetbrains.kotlin.test.JetTestUtils;
22+
import org.jetbrains.kotlin.test.TestMetadata;
23+
import org.junit.runner.RunWith;
24+
25+
import java.io.File;
26+
import java.util.regex.Pattern;
27+
28+
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
29+
@SuppressWarnings("all")
30+
@TestMetadata("idea/testData/navigation/gotoTestOrCode")
31+
@TestDataPath("$PROJECT_ROOT")
32+
@RunWith(JUnit3RunnerWithInners.class)
33+
public class GotoTestOrCodeActionTestGenerated extends AbstractGotoTestOrCodeActionTest {
34+
public void testAllFilesPresentInGotoTestOrCode() throws Exception {
35+
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/navigation/gotoTestOrCode"), Pattern.compile("^(.+)\\.main\\..+$"), true);
36+
}
37+
38+
@TestMetadata("fromJavaClassToTest.main.java")
39+
public void testFromJavaClassToTest() throws Exception {
40+
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromJavaClassToTest.main.java");
41+
doTest(fileName);
42+
}
43+
44+
@TestMetadata("fromJavaTestToKotlinClass.main.java")
45+
public void testFromJavaTestToKotlinClass() throws Exception {
46+
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinClass.main.java");
47+
doTest(fileName);
48+
}
49+
50+
@TestMetadata("fromKotlinClassToTest.main.kt")
51+
public void testFromKotlinClassToTest() throws Exception {
52+
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinClassToTest.main.kt");
53+
doTest(fileName);
54+
}
55+
56+
@TestMetadata("fromKotlinTestToJavaClass.main.kt")
57+
public void testFromKotlinTestToJavaClass() throws Exception {
58+
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinTestToJavaClass.main.kt");
59+
doTest(fileName);
60+
}
61+
62+
@TestMetadata("fromKotlinTestToKotlinClass.main.kt")
63+
public void testFromKotlinTestToKotlinClass() throws Exception {
64+
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinClass.main.kt");
65+
doTest(fileName);
66+
}
67+
}

idea/tests/org/jetbrains/kotlin/idea/navigation/AbstractKotlinGotoImplementationTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ public abstract class AbstractKotlinGotoImplementationTest : LightCodeInsightTes
3232
protected fun doTest(path: String) {
3333
configureByFile(path)
3434
val gotoData = NavigationTestUtils.invokeGotoImplementations(LightPlatformCodeInsightTestCase.getEditor(), LightPlatformCodeInsightTestCase.getFile())
35-
NavigationTestUtils.assertGotoImplementations(LightPlatformCodeInsightTestCase.getEditor(), gotoData)
35+
NavigationTestUtils.assertGotoDataMatching(LightPlatformCodeInsightTestCase.getEditor(), gotoData)
3636
}
3737
}

0 commit comments

Comments
 (0)