Better testing via logging of performed operations

This commit is contained in:
Valentin Kipyatkov
2016-08-28 01:25:37 +03:00
parent 1e80ad3f98
commit 2ca163a375
11 changed files with 185 additions and 14 deletions
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName
import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference import org.jetbrains.kotlin.idea.references.KtDestructuringDeclarationReference
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinRequestResultProcessor
import org.jetbrains.kotlin.idea.search.restrictToKotlinSources import org.jetbrains.kotlin.idea.search.restrictToKotlinSources
@@ -61,7 +62,9 @@ enum class DestructuringDeclarationUsageSearch {
ALWAYS_SMART, ALWAYS_PLAIN, PLAIN_WHEN_NEEDED ALWAYS_SMART, ALWAYS_PLAIN, PLAIN_WHEN_NEEDED
} }
// for tests
var destructuringDeclarationUsageSearchMode = if (ApplicationManager.getApplication().isUnitTestMode) ALWAYS_SMART else PLAIN_WHEN_NEEDED var destructuringDeclarationUsageSearchMode = if (ApplicationManager.getApplication().isUnitTestMode) ALWAYS_SMART else PLAIN_WHEN_NEEDED
var destructuringDeclarationUsageSearchLog: MutableList<String>? = null
//TODO: check if it's too expensive //TODO: check if it's too expensive
@@ -108,7 +111,8 @@ fun findDestructuringDeclarationUsages(
ktDeclaration, ktDeclaration,
scope, scope,
consumer, consumer,
plainSearchHandler = { searchScope -> doPlainSearch(ktDeclaration, searchScope, optimizer) } plainSearchHandler = { searchScope -> doPlainSearch(ktDeclaration, searchScope, optimizer) },
testLog = destructuringDeclarationUsageSearchLog
).run() ).run()
} }
@@ -124,13 +128,15 @@ private class Processor(
private val target: KtDeclaration, private val target: KtDeclaration,
private val searchScope: SearchScope, private val searchScope: SearchScope,
private val consumer: Processor<PsiReference>, private val consumer: Processor<PsiReference>,
private val plainSearchHandler: (SearchScope) -> Unit plainSearchHandler: (SearchScope) -> Unit,
private val testLog: MutableList<String>?
) { ) {
private val project = target.project private val plainSearchHandler: (SearchScope) -> Unit = { scope ->
testLog?.add("Used plain search in ${scope.logPresentation()}")
plainSearchHandler(scope)
}
// we don't need to search usages of declarations in Java because Java doesn't have implicitly typed declarations so such usages cannot affect Kotlin code private val project = target.project
//TODO: what about Scala and other JVM-languages?
private val declarationUsageScope = GlobalSearchScope.projectScope(project).restrictToKotlinSources()
// note: a Task must define equals & hashCode! // note: a Task must define equals & hashCode!
private interface Task { private interface Task {
@@ -159,8 +165,10 @@ private class Processor(
val parameters = ClassInheritorsSearch.SearchParameters(psiClass, GlobalSearchScope.allScope(project), true, true, false) val parameters = ClassInheritorsSearch.SearchParameters(psiClass, GlobalSearchScope.allScope(project), true, true, false)
val classesToSearch = listOf(psiClass) + ClassInheritorsSearch.search(parameters).findAll() val classesToSearch = listOf(psiClass) + ClassInheritorsSearch.search(parameters).findAll()
testLog?.add("Searched inheritors of ${psiClass.logPresentation()}")
for (classToSearch in classesToSearch) { for (classToSearch in classesToSearch) {
testLog?.add("Searched references to ${classToSearch.logPresentation()}")
ReferencesSearch.search(classToSearch).forEach(Processor processor@ { reference -> //TODO: see KT-13607 ReferencesSearch.search(classToSearch).forEach(Processor processor@ { reference -> //TODO: see KT-13607
if (processDataClassUsage(reference)) return@processor true if (processDataClassUsage(reference)) return@processor true
@@ -211,7 +219,11 @@ private class Processor(
private fun addCallableDeclarationToProcess(declaration: PsiElement, kind: CallableToProcessKind) { private fun addCallableDeclarationToProcess(declaration: PsiElement, kind: CallableToProcessKind) {
data class ProcessCallableUsagesTask(val declaration: PsiElement, val kind: CallableToProcessKind) : Task { data class ProcessCallableUsagesTask(val declaration: PsiElement, val kind: CallableToProcessKind) : Task {
override fun perform() { override fun perform() {
ReferencesSearch.search(declaration, declarationUsageScope).forEach { reference -> // we don't need to search usages of declarations in Java because Java doesn't have implicitly typed declarations so such usages cannot affect Kotlin code
//TODO: what about Scala and other JVM-languages?
val scope = GlobalSearchScope.projectScope(project).restrictToKotlinSources()
testLog?.add("Searched references to ${declaration.logPresentation()} in Kotlin files")
ReferencesSearch.search(declaration, scope).forEach { reference ->
when (kind) { when (kind) {
CallableToProcessKind.HAS_DATA_CLASS_TYPE -> { CallableToProcessKind.HAS_DATA_CLASS_TYPE -> {
if (reference is KtDestructuringDeclarationReference) { if (reference is KtDestructuringDeclarationReference) {
@@ -247,6 +259,7 @@ private class Processor(
override fun perform() { override fun perform() {
//TODO: what about other JVM languages? //TODO: what about other JVM languages?
val scope = GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.projectScope(project), JavaFileType.INSTANCE) val scope = GlobalSearchScope.getScopeRestrictedByFileTypes(GlobalSearchScope.projectScope(project), JavaFileType.INSTANCE)
testLog?.add("Searched references to ${psiClass.logPresentation()} in java files")
ReferencesSearch.search(psiClass, scope).forEach { reference -> ReferencesSearch.search(psiClass, scope).forEach { reference ->
// check if the reference is method parameter type // check if the reference is method parameter type
val parameter = ((reference as? PsiJavaCodeReferenceElement)?.parent as? PsiTypeElement)?.parent as? PsiParameter val parameter = ((reference as? PsiJavaCodeReferenceElement)?.parent as? PsiTypeElement)?.parent as? PsiParameter
@@ -563,4 +576,43 @@ private class Processor(
else -> false else -> false
} }
} }
private fun PsiElement.logPresentation(): String? {
val fqName = getKotlinFqName()?.asString()
?: (this as? KtNamedDeclaration)?.name
return when (this) {
is PsiMethod, is KtFunction -> fqName + "()"
is KtParameter -> "parameter ${this.name} in ${this.ownerFunction?.logPresentation()}"
else -> fqName
}
}
private fun SearchScope.logPresentation(): String {
return when (this) {
searchScope -> "whole search scope"
is LocalSearchScope -> {
scope
.map { element ->
" " + when (element) {
is KtFunctionLiteral -> element.text
is KtWhenEntry -> {
if (element.isElse)
"KtWhenEntry \"else\""
else
"KtWhenEntry \"" + element.conditions.joinToString(", ") { it.text } + "\""
}
is KtNamedDeclaration -> element.node.elementType.toString() + ":" + element.name
else -> element.toString()
}
}
.toList()
.sorted()
.joinToString("\n", "LocalSearchScope:\n")
}
else -> this.displayName
}
}
} }
@@ -131,9 +131,10 @@ abstract class KotlinFindMemberUsagesHandler<T : KtNamedDeclaration>
val uniqueProcessor = CommonProcessors.UniqueProcessor(processor) val uniqueProcessor = CommonProcessors.UniqueProcessor(processor)
if (options.isUsages) { if (options.isUsages) {
val searchParameters = KotlinReferencesSearchParameters(element, // we disable searchForComponentConventions for ReferencesSearch because they will be searched by MethodReferencesSearch
scope = options.searchScope, val kotlinSearchOptions = createKotlinReferencesSearchOptions(options)
kotlinOptions = createKotlinReferencesSearchOptions(options)) .copy(searchForComponentConventions = false)
val searchParameters = KotlinReferencesSearchParameters(element, options.searchScope, kotlinOptions = kotlinSearchOptions)
with(applyQueryFilters(element, options, ReferencesSearch.search(searchParameters))) { with(applyQueryFilters(element, options, ReferencesSearch.search(searchParameters))) {
if (!forEach(referenceProcessor)) return false if (!forEach(referenceProcessor)) return false
@@ -0,0 +1,7 @@
Searched inheritors of X
Searched references to X
Searched references to f() in Kotlin files
Used plain search in LocalSearchScope:
CLASS:X
FUN:component1
FUN:component2
@@ -0,0 +1,9 @@
Searched inheritors of X
Searched references to X
Searched references to f() in Kotlin files
Used plain search in LocalSearchScope:
CLASS:X
FUN:component1
FUN:component1
FUN:component2
FUN:component2
@@ -0,0 +1,53 @@
Searched inheritors of pack.A
Searched inheritors of pack.B
Searched inheritors of pack.C
Searched references to JavaClass.getA() in Kotlin files
Searched references to JavaClass.takeSAM() in Kotlin files
Searched references to JavaSAM in java files
Searched references to a in Kotlin files
Searched references to a in Kotlin files
Searched references to a1 in Kotlin files
Searched references to f() in Kotlin files
Searched references to g() in Kotlin files
Searched references to h() in Kotlin files
Searched references to list in Kotlin files
Searched references to listOfA() in Kotlin files
Searched references to pack.A
Searched references to pack.B
Searched references to pack.C
Searched references to pack.C.component1() in Kotlin files
Searched references to pack4.takeExtFun() in Kotlin files
Searched references to pack4.takeFun1() in Kotlin files
Searched references to pack4.takeFun2() in Kotlin files
Searched references to pack4.takeFun3() in Kotlin files
Searched references to pack4.takeFuns() in Kotlin files
Searched references to pack5.v in Kotlin files
Searched references to parameter a in B() in Kotlin files
Searched references to parameter a in null in Kotlin files
Searched references to parameter b in pack.f() in Kotlin files
Searched references to parameter c in pack.f() in Kotlin files
Searched references to parameter p in pack4.foo() in Kotlin files
Searched references to parameter p in pack4.takeExtFun() in Kotlin files
Searched references to parameter p in pack4.takeFun1() in Kotlin files
Searched references to parameter p in pack4.takeFun2() in Kotlin files
Searched references to parameter p in pack4.takeFun3() in Kotlin files
Searched references to parameter p in pack4.takeFuns() in Kotlin files
Used plain search in LocalSearchScope:
CLASS:A
FUN:ext1
FUN:ext1
FUN:x
KtWhenEntry "else"
KtWhenEntry "is A"
{ a, n -> val (x, y) = a!! }
{ val (x, y ) = it }
{ val (x, y) = it }
{ val (x, y) = it }
{ val (x, y) = it }
{ val (x, y) = it }
{ val (x, y) = it[0] }
{ val (x, y) = this }
Used plain search in LocalSearchScope:
CLASS:B
Used plain search in LocalSearchScope:
CLASS:C
@@ -0,0 +1,5 @@
Searched inheritors of A
Searched references to A
Searched references to a in Kotlin files
Used plain search in LocalSearchScope:
CLASS:A
@@ -0,0 +1 @@
Used plain search in whole search scope
@@ -0,0 +1,10 @@
Searched inheritors of X
Searched references to X
Searched references to Y
Searched references to f() in Kotlin files
Used plain search in LocalSearchScope:
CLASS:X
CLASS:Y
FUN:component1
FUN:component2
FUN:ext
@@ -0,0 +1,11 @@
Searched inheritors of X
Searched references to X
Searched references to Y
Searched references to Z
Searched references to f() in Kotlin files
Searched references to g() in Kotlin files
Searched references to h() in Kotlin files
Used plain search in LocalSearchScope:
CLASS:X
CLASS:Y
CLASS:Z
@@ -0,0 +1,4 @@
Searched inheritors of test.KotlinDataClass
Searched references to test.KotlinDataClass
Used plain search in LocalSearchScope:
CLASS:KotlinDataClass
@@ -58,6 +58,7 @@ import org.jetbrains.kotlin.idea.findUsages.KotlinClassFindUsagesOptions;
import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory; import org.jetbrains.kotlin.idea.findUsages.KotlinFindUsagesHandlerFactory;
import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions; import org.jetbrains.kotlin.idea.findUsages.KotlinFunctionFindUsagesOptions;
import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions; import org.jetbrains.kotlin.idea.findUsages.KotlinPropertyFindUsagesOptions;
import org.jetbrains.kotlin.idea.search.usagesSearch.DestructuringDeclarationUsagesKt;
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase; import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase;
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor; import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor;
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase; import org.jetbrains.kotlin.idea.test.PluginTestCaseBase;
@@ -69,9 +70,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils;
import java.io.File; import java.io.File;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.util.ArrayList; import java.util.*;
import java.util.Collection;
import java.util.List;
public abstract class AbstractFindUsagesTest extends KotlinLightCodeInsightFixtureTestCase { public abstract class AbstractFindUsagesTest extends KotlinLightCodeInsightFixtureTestCase {
@@ -399,7 +398,22 @@ public abstract class AbstractFindUsagesTest extends KotlinLightCodeInsightFixtu
) throws ClassNotFoundException, InstantiationException, IllegalAccessException { ) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
boolean highlightingMode = InTextDirectivesUtils.isDirectiveDefined(mainFileText, "// HIGHLIGHTING"); boolean highlightingMode = InTextDirectivesUtils.isDirectiveDefined(mainFileText, "// HIGHLIGHTING");
Collection<UsageInfo> usageInfos = findUsages(caretElement, options, highlightingMode); Collection<UsageInfo> usageInfos;
String log = null;
try {
DestructuringDeclarationUsagesKt.setDestructuringDeclarationUsageSearchLog(new ArrayList<String>());
usageInfos = findUsages(caretElement, options, highlightingMode);
}
finally {
List<String> logList = DestructuringDeclarationUsagesKt.getDestructuringDeclarationUsageSearchLog();
assert logList != null;
DestructuringDeclarationUsagesKt.setDestructuringDeclarationUsageSearchLog(null);
if (logList.size() > 0) {
Collections.sort(logList);
log = StringUtil.join(logList, "\n");
}
}
Collection<UsageFilteringRule> filteringRules = instantiateClasses(mainFileText, "// FILTERING_RULES: "); Collection<UsageFilteringRule> filteringRules = instantiateClasses(mainFileText, "// FILTERING_RULES: ");
final Collection<UsageGroupingRule> groupingRules = instantiateClasses(mainFileText, "// GROUPING_RULES: "); final Collection<UsageGroupingRule> groupingRules = instantiateClasses(mainFileText, "// GROUPING_RULES: ");
@@ -458,6 +472,10 @@ public abstract class AbstractFindUsagesTest extends KotlinLightCodeInsightFixtu
Collection<String> finalUsages = Ordering.natural().sortedCopy(Collections2.transform(filteredUsages, convertToString)); Collection<String> finalUsages = Ordering.natural().sortedCopy(Collections2.transform(filteredUsages, convertToString));
KotlinTestUtils.assertEqualsToFile(new File(rootPath, prefix + "results.txt"), StringUtil.join(finalUsages, "\n")); KotlinTestUtils.assertEqualsToFile(new File(rootPath, prefix + "results.txt"), StringUtil.join(finalUsages, "\n"));
if (log != null) {
KotlinTestUtils.assertEqualsToFile(new File(rootPath, prefix + "log"), log);
}
} }
protected Collection<UsageInfo> findUsages( protected Collection<UsageInfo> findUsages(