Unit Test Tooling: Test navigation

#KT-6472 In Progress
This commit is contained in:
Alexey Sedunov
2015-10-07 17:25:13 +03:00
parent ad929a6577
commit ae9bd371e6
18 changed files with 346 additions and 3 deletions
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.generators.tests.generator.TestGenerator.TargetBacke
import org.jetbrains.kotlin.generators.tests.reservedWords.generateTestDataForReservedWords
import org.jetbrains.kotlin.idea.AbstractExpressionSelectionTest
import org.jetbrains.kotlin.idea.AbstractSmartSelectionTest
import org.jetbrains.kotlin.idea.actions.AbstractGotoTestOrCodeActionTest
import org.jetbrains.kotlin.idea.codeInsight.*
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateActionTest
import org.jetbrains.kotlin.idea.codeInsight.moveUpDown.AbstractCodeMoverTest
@@ -411,6 +412,10 @@ fun main(args: Array<String>) {
model("navigation/implementations", recursive = false)
}
testClass<AbstractGotoTestOrCodeActionTest>() {
model("navigation/gotoTestOrCode", pattern = "^(.+)\\.main\\..+\$")
}
testClass<AbstractQuickFixMultiFileTest>() {
model("quickfix", pattern = """^(\w+)\.before\.Main\.\w+$""", testMethod = "doTestWithExtraFile")
}
+3
View File
@@ -612,6 +612,9 @@
<codeInsight.typeInfo language="kotlin" implementationClass="org.jetbrains.kotlin.idea.codeInsight.KotlinExpressionTypeProvider"/>
<testCreator language="kotlin" implementationClass="org.jetbrains.kotlin.idea.testIntegration.KotlinTestCreator"/>
<testFinder implementation="org.jetbrains.kotlin.idea.testIntegration.KotlinTestFinder"/>
<intentionAction>
<className>org.jetbrains.kotlin.idea.intentions.IfNullToElvisIntention</className>
<category>Kotlin</category>
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.idea.testIntegration
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import com.intellij.testIntegration.TestCreator
class KotlinTestCreator : TestCreator {
override fun isAvailable(project: Project, editor: Editor, file: PsiFile): Boolean {
return KotlinCreateTestIntention().isAvailable(project, editor, file)
}
override fun createTest(project: Project, editor: Editor, file: PsiFile) {
KotlinCreateTestIntention().invoke(project, editor, file)
}
}
@@ -0,0 +1,102 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.idea.testIntegration
import com.intellij.codeInsight.TestFrameworks
import com.intellij.openapi.util.Pair
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.search.PsiShortNamesCache
import com.intellij.testIntegration.JavaTestFinder
import com.intellij.testIntegration.TestFinderHelper
import com.intellij.util.CommonProcessors
import com.intellij.util.containers.HashSet
import org.jetbrains.kotlin.asJava.KotlinLightClassForExplicitDeclaration
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.psi.JetClassOrObject
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import java.util.*
import java.util.regex.Pattern
// Based on com.intellij.testIntegration.JavaTestFinder.JavaTestFinder implementation
// TODO: We can reuse JavaTestFinder if Kotlin classes have their isPhysical() return true
class KotlinTestFinder : JavaTestFinder() {
override fun findSourceElement(from: PsiElement): PsiClass? {
super.findSourceElement(from)?.let { return it }
val classOrObject = from.parentsWithSelf.filterIsInstance<JetClassOrObject>().firstOrNull { !it.isLocal() } ?: return null
if (classOrObject.resolveToDescriptorIfAny() == null) return null
return classOrObject.toLightClass()
}
override fun isTest(element: PsiElement): Boolean {
val sourceElement = findSourceElement(element) ?: return false
return super.isTest(sourceElement)
}
override fun findClassesForTest(element: PsiElement): Collection<PsiElement> {
val klass = findSourceElement(element) ?: return emptySet()
val scope = getSearchScope(element, true)
val cache = PsiShortNamesCache.getInstance(element.project)
val frameworks = TestFrameworks.getInstance()
val classesWithWeights = ArrayList<Pair<out PsiNamedElement, Int>>()
for (candidateNameWithWeight in TestFinderHelper.collectPossibleClassNamesWithWeights(klass.name)) {
for (eachClass in cache.getClassesByName(candidateNameWithWeight.first, scope)) {
if (eachClass.isAnnotationType || frameworks.isTestClass(eachClass)) continue
if (!eachClass.isPhysical && eachClass !is KotlinLightClassForExplicitDeclaration) continue
classesWithWeights.add(Pair.create(eachClass, candidateNameWithWeight.second))
}
}
return TestFinderHelper.getSortedElements(classesWithWeights, false)
}
override fun findTestsForClass(element: PsiElement): Collection<PsiElement> {
val klass = findSourceElement(element) ?: return emptySet()
val classesWithProximities = ArrayList<Pair<out PsiNamedElement, Int>>()
val processor = CommonProcessors.CollectProcessor(classesWithProximities)
val klassName = klass.name!!
val pattern = Pattern.compile(".*" + StringUtil.escapeToRegexp(klassName) + ".*", Pattern.CASE_INSENSITIVE)
val scope = getSearchScope(klass, false)
val frameworks = TestFrameworks.getInstance()
val cache = PsiShortNamesCache.getInstance(klass.project)
val names = HashSet<String>()
cache.getAllClassNames(names)
for (candidateName in names) {
if (!pattern.matcher(candidateName).matches()) continue
for (candidateClass in cache.getClassesByName(candidateName, scope)) {
if (!(frameworks.isTestClass(candidateClass) || frameworks.isPotentialTestClass(candidateClass))) continue
if (!candidateClass.isPhysical && candidateClass !is KotlinLightClassForExplicitDeclaration) continue
processor.process(Pair.create(candidateClass, TestFinderHelper.calcTestNameProximity(klassName, candidateName)))
}
}
return TestFinderHelper.getSortedElements(classesWithProximities, true)
}
}
@@ -0,0 +1,3 @@
import junit.framework.TestCase
class FooTest2 : TestCase()
@@ -0,0 +1,12 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
// REF: (<root>).FooTest2
// REF: FooTest
import junit.framework.TestCase;
public class <caret>Foo {
}
public class FooTest extends TestCase {
}
@@ -0,0 +1,5 @@
import junit.framework.TestCase
class Foo
class FooTest : TestCase()
@@ -0,0 +1,7 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
// REF: (<root>).Foo
import junit.framework.TestCase;
public class <caret>FooTest extends TestCase {
}
@@ -0,0 +1,5 @@
import junit.framework.TestCase;
public class FooTest2 extends TestCase {
}
@@ -0,0 +1,8 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
// REF: (<root>).FooTest
// REF: FooTest2
import junit.framework.TestCase
class <caret>Foo
class FooTest : TestCase()
@@ -0,0 +1,9 @@
import junit.framework.TestCase;
public class Foo {
}
public class FooTest2 extends TestCase {
}
@@ -0,0 +1,5 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
// REF: Foo
import junit.framework.TestCase
class <caret>FooTest2 : TestCase()
@@ -0,0 +1,9 @@
// CONFIGURE_LIBRARY: JUnit@lib/junit-4.12.jar
// REF: (<root>).Foo
import junit.framework.TestCase
class Foo
class <caret>FooTest : TestCase()
class FooTest2 : TestCase()
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.idea.actions
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiFile
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testIntegration.GotoTestOrCodeHandler
import org.jetbrains.kotlin.idea.navigation.NavigationTestUtils
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.JetWithJdkAndRuntimeLightProjectDescriptor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.JetTestUtils
import java.io.File
abstract class AbstractGotoTestOrCodeActionTest : JetLightCodeInsightFixtureTestCase() {
private object Handler: GotoTestOrCodeHandler() {
public override fun getSourceAndTargetElements(editor: Editor?, file: PsiFile?) = super.getSourceAndTargetElements(editor, file)
}
override fun getProjectDescriptor() = JetWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
protected fun doTest(path: String) {
val mainFile = File(path)
val fileText = FileUtil.loadFile(mainFile, true)
val addKotlinRuntime = InTextDirectivesUtils.findStringWithPrefixes(fileText, "// WITH_RUNTIME") != null
try {
if (addKotlinRuntime) {
ConfigLibraryUtil.configureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
ConfigLibraryUtil.configureLibrariesByDirective(myModule, PlatformTestUtil.getCommunityPath(), fileText)
myFixture.testDataPath = "${JetTestUtils.getHomeDirectory()}/${mainFile.getParent()}"
val mainFileName = mainFile.name
val mainFileBaseName = mainFileName.substring(0, mainFileName.indexOf('.'))
mainFile.parentFile
.listFiles { file, name ->
name != mainFileName && name.startsWith("$mainFileBaseName.") && (name.endsWith(".kt") || name.endsWith(".java"))
}
.forEach{ myFixture.configureByFile(it.name) }
val file = myFixture.configureByFile(mainFileName)
NavigationTestUtils.assertGotoDataMatching(editor, Handler.getSourceAndTargetElements(editor, file))
}
finally {
ConfigLibraryUtil.unconfigureLibrariesByDirective(myModule, fileText)
if (addKotlinRuntime) {
ConfigLibraryUtil.unConfigureKotlinRuntimeAndSdk(myModule, PluginTestCaseBase.mockJdk())
}
}
}
}
@@ -0,0 +1,67 @@
/*
* Copyright 2010-2015 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 org.jetbrains.kotlin.idea.actions;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.JetTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/navigation/gotoTestOrCode")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class GotoTestOrCodeActionTestGenerated extends AbstractGotoTestOrCodeActionTest {
public void testAllFilesPresentInGotoTestOrCode() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/navigation/gotoTestOrCode"), Pattern.compile("^(.+)\\.main\\..+$"), true);
}
@TestMetadata("fromJavaClassToTest.main.java")
public void testFromJavaClassToTest() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromJavaClassToTest.main.java");
doTest(fileName);
}
@TestMetadata("fromJavaTestToKotlinClass.main.java")
public void testFromJavaTestToKotlinClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromJavaTestToKotlinClass.main.java");
doTest(fileName);
}
@TestMetadata("fromKotlinClassToTest.main.kt")
public void testFromKotlinClassToTest() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinClassToTest.main.kt");
doTest(fileName);
}
@TestMetadata("fromKotlinTestToJavaClass.main.kt")
public void testFromKotlinTestToJavaClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinTestToJavaClass.main.kt");
doTest(fileName);
}
@TestMetadata("fromKotlinTestToKotlinClass.main.kt")
public void testFromKotlinTestToKotlinClass() throws Exception {
String fileName = JetTestUtils.navigationMetadata("idea/testData/navigation/gotoTestOrCode/fromKotlinTestToKotlinClass.main.kt");
doTest(fileName);
}
}
@@ -32,6 +32,6 @@ public abstract class AbstractKotlinGotoImplementationTest : LightCodeInsightTes
protected fun doTest(path: String) {
configureByFile(path)
val gotoData = NavigationTestUtils.invokeGotoImplementations(LightPlatformCodeInsightTestCase.getEditor(), LightPlatformCodeInsightTestCase.getFile())
NavigationTestUtils.assertGotoImplementations(LightPlatformCodeInsightTestCase.getEditor(), gotoData)
NavigationTestUtils.assertGotoDataMatching(LightPlatformCodeInsightTestCase.getEditor(), gotoData)
}
}
@@ -83,7 +83,7 @@ public class JetGotoImplementationMultifileTest extends JetLightCodeInsightFixtu
private void doMultifileTest(String ... fileNames) throws Exception {
myFixture.configureByFiles(fileNames);
GotoTargetHandler.GotoData gotoData = NavigationTestUtils.invokeGotoImplementations(getEditor(), getFile());
NavigationTestUtils.assertGotoImplementations(getEditor(), gotoData);
NavigationTestUtils.assertGotoDataMatching(getEditor(), gotoData);
}
@Override
@@ -48,7 +48,7 @@ public final class NavigationTestUtils {
return new GotoImplementationHandler().getSourceAndTargetElements(editor, psiFile);
}
public static void assertGotoImplementations(Editor editor, GotoTargetHandler.GotoData gotoData) {
public static void assertGotoDataMatching(Editor editor, GotoTargetHandler.GotoData gotoData) {
// Get expected references from the tested document
List<String> expectedReferences = InTextDirectivesUtils.findListWithPrefixes(editor.getDocument().getText(), "// REF:");
Collections.sort(expectedReferences);