[threading] IJPL-179707: Fix testing suite

GitOrigin-RevId: 55783f7b0c474b390e9372d2dda144c7966642bf
This commit is contained in:
Moncef Slimani
2025-11-17 13:31:32 +00:00
committed by intellij-monorepo-bot
parent e38e358490
commit 6c81db53bc
24 changed files with 188 additions and 298 deletions
@@ -741,7 +741,7 @@
<toolWindow
id="Locking Requirements"
factoryClass="org.jetbrains.idea.devkit.threadingModelHelper.LockReqsToolWindowFactoryCompose"
factoryClass="org.jetbrains.idea.devkit.threadingModelHelper.LockReqsToolWindowFactory"
anchor="bottom"/>
<!-- INTERNAL -->
@@ -934,6 +934,7 @@
<action internal="true" class="org.jetbrains.idea.devkit.actions.ShowHelpPageByIdAction" id="ShowHelpPageById"/>
<action id="AnalyzeUnloadablePlugins" internal="true" class="org.jetbrains.idea.devkit.internal.AnalyzeUnloadablePluginsAction"/>
<action id="LockReqsAnalysis" internal="true" class="org.jetbrains.idea.devkit.threadingModelHelper.LockReqsAction"/>
<action id="BFLReqsAction" internal="true" class="org.jetbrains.idea.devkit.threadingModelHelper.bfl.BFLReqsAction"/>
<add-to-group group-id="Internal" anchor="last"/>
</group>
@@ -384,6 +384,8 @@ action.ShowHelpPageById.dialog.enter.help.id=Enter Help ID:
action.ShowHelpPageById.dialog.title=Show Help Page
action.BFLReqsAction.text=BulkFileListener EDT Checker
action.ShowSerializedXml.text=Show Serialized XML for Class
action.ShowSerializedXml.message.caret.must.be.at.class.identifier=Caret should be positioned at the name of the class
action.ShowSerializedXml.message.cannot.find.class=Cannot find class ''{0}''
@@ -10,9 +10,9 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
private data class TraversalContext(
val config: AnalysisConfig,
val paths: MutableList<ExecutionPath> = mutableListOf(),
val currentPath: MutableList<MethodCall> = mutableListOf(),
val visited: MutableSet<MethodSignature> = mutableSetOf(),
val paths: MutableSet<ExecutionPath> = mutableSetOf(),
val messageBusTopics: MutableSet<String> = mutableSetOf(),
val swingComponents: MutableSet<MethodSignature> = mutableSetOf(),
)
@@ -33,7 +33,7 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
val annotationRequirements = detector.findAnnotationRequirements(method)
annotationRequirements.forEach { context.paths.add(ExecutionPath(context.currentPath.toList(), it)) }
processMethodBody(method, context)
context.currentPath.removeAt(context.currentPath.lastIndex)
context.currentPath.removeLast()
}
@@ -46,16 +46,17 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
super.visitMethodCallExpression(expression)
val resolvedMethod = expression.resolveMethod() ?: return
localRequirements += detector.findBodyRequirements(resolvedMethod, expression)
if (localRequirements.any { it.requirementReason == RequirementReason.SWING_COMPONENT }) context.swingComponents.add(MethodSignature.fromMethod(resolvedMethod))
localRequirements += detector.findBodyRequirements(resolvedMethod)
if (localRequirements.any { it.requirementReason == RequirementReason.SWING_COMPONENT }) {
context.swingComponents.add(MethodSignature.fromMethod(resolvedMethod))
}
handleMethodCall(resolvedMethod, expression, context)
}
override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) {
super.visitMethodReferenceExpression(expression)
(expression.resolve() as? PsiMethod)?.let { resolvedMethod ->
localRequirements += detector.findBodyRequirements(resolvedMethod, expression)
localRequirements += detector.findBodyRequirements(resolvedMethod)
if (!detector.isAsyncBoundary(resolvedMethod)) traverseMethod(resolvedMethod, context)
}
}
@@ -69,7 +70,8 @@ class LockReqsAnalyzer(private val detector: LockReqsDetector = LockReqsDetector
})
localRequirements.forEach { requirement ->
val path = ExecutionPath(context.currentPath.toList(), requirement, isSpeculative = context.currentPath.any { it.isPolymorphic || it.isMessageBusCall })
val path = ExecutionPath(context.currentPath.toList(), requirement,
context.currentPath.any { it.isPolymorphic || it.isMessageBusCall })
context.paths.add(path)
}
}
@@ -20,14 +20,14 @@ class LockReqsDetector(private val patterns: LockReqsPatterns = DefaultLockReqsP
return requirements
}
fun findBodyRequirements(method: PsiMethod, source: PsiElement): List<LockRequirement> {
fun findBodyRequirements(method: PsiMethod): List<LockRequirement> {
val requirements = mutableListOf<LockRequirement>()
val className = method.containingClass?.qualifiedName
val methodName = method.name
patterns.assertionMethods[className]?.get(methodName)?.let { lockType ->
requirements.add(LockRequirement(source, lockType, RequirementReason.ASSERTION))
requirements.add(LockRequirement(method, lockType, RequirementReason.ASSERTION))
}
if (isSwingMethod(method)) requirements.add(LockRequirement(source, LockType.EDT, RequirementReason.SWING_COMPONENT))
if (isSwingMethod(method)) requirements.add(LockRequirement(method, LockType.EDT, RequirementReason.SWING_COMPONENT))
return requirements
}
@@ -46,7 +46,7 @@ data class ExecutionPath(
data class AnalysisResult(
val method: PsiMethod,
val paths: List<ExecutionPath>,
val paths: Set<ExecutionPath>,
val messageBusTopics: Set<String> = emptySet(),
val swingComponents: Set<MethodSignature> = emptySet(),
)
@@ -1,133 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.threadingModelHelper
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiMethodCallExpression
import com.intellij.psi.JavaRecursiveElementVisitor
import com.intellij.psi.PsiMethodReferenceExpression
@Deprecated("LockReqsAnalyzer")
class LockReqsObsolete {
companion object {
private const val ASSERT_READ_ACCESS_METHOD = "assertReadAccess"
private const val THREADING_ASSERTIONS_CLASS = "com.intellij.util.concurrency.ThreadingAssertions"
private const val REQUIRES_READ_LOCK_ANNOTATION = "com.intellij.util.concurrency.annotations.RequiresReadLock"
private const val MAX_PATH_DEPTH = 1000
private val ASYNC_METHODS = setOf("invokeLater", "invokeAndWait", "runInEdt")
enum class LockCheckType {
ANNOTATION, ASSERTION
}
data class LockRequirement(
val type: LockCheckType,
val method: PsiMethod,
)
data class ExecutionPath(
val methodChain: List<PsiMethod>,
val lockRequirement: LockRequirement,
) {
val pathString: String
get() = buildString {
append(methodChain.joinToString(" -> ") {
"${it.containingClass?.name}.${it.name}"
})
append(" -> ")
when (lockRequirement.type) {
LockCheckType.ANNOTATION -> append("@RequiresReadLock")
LockCheckType.ASSERTION -> append("ThreadingAssertions.assertReadAccess()")
}
}
}
data class AnalysisResult(
val method: PsiMethod,
val paths: List<ExecutionPath>,
)
}
private val processed = mutableSetOf<PsiMethod>()
fun analyzeMethod(method: PsiMethod): List<ExecutionPath> {
processed.clear()
val paths = mutableListOf<ExecutionPath>()
val currentPath = mutableListOf<PsiMethod>()
processMethodDFS(method, currentPath, paths)
return paths
}
private fun processMethodDFS(
method: PsiMethod,
currentPath: MutableList<PsiMethod>,
paths: MutableList<ExecutionPath>,
) {
if (method in processed || currentPath.size > MAX_PATH_DEPTH) return
processed.add(method)
currentPath.add(method)
findLockChecks(method).forEach { check ->
paths.add(ExecutionPath(currentPath.toList(), LockRequirement(check, method)))
}
getMethodCallees(method).forEach {
if (!isAsyncMethod(it)) {
processMethodDFS(it, currentPath, paths)
}
}
currentPath.removeAt(currentPath.lastIndex)
}
private fun isAsyncMethod(method: PsiMethod): Boolean {
return method.name in ASYNC_METHODS
}
private fun getMethodCallees(method: PsiMethod): List<PsiMethod> {
val callees = mutableListOf<PsiMethod>()
method.body?.accept(object : JavaRecursiveElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
super.visitMethodCallExpression(expression)
expression.resolveMethod()?.let { callees.add(it) }
}
override fun visitMethodReferenceExpression(expression: PsiMethodReferenceExpression) {
super.visitMethodReferenceExpression(expression)
(expression.resolve() as? PsiMethod)?.let { callees.add(it) }
}
})
return callees
}
private fun findLockChecks(method: PsiMethod): List<LockCheckType> {
return buildList {
if (hasRequiresReadLockAnnotation(method)) add(LockCheckType.ANNOTATION)
if (hasAssertReadAccessCall(method)) add(LockCheckType.ASSERTION)
}
}
private fun hasRequiresReadLockAnnotation(method: PsiMethod): Boolean {
return method.hasAnnotation(REQUIRES_READ_LOCK_ANNOTATION)
}
private fun isAssertReadAccess(expression: PsiMethodCallExpression): Boolean {
return ASSERT_READ_ACCESS_METHOD == expression.methodExpression.referenceName &&
THREADING_ASSERTIONS_CLASS == expression.resolveMethod()?.containingClass?.qualifiedName
}
private fun hasAssertReadAccessCall(method: PsiMethod): Boolean {
var found = false
method.body?.accept(object : JavaRecursiveElementVisitor() {
override fun visitMethodCallExpression(expression: PsiMethodCallExpression) {
if (!found) {
super.visitMethodCallExpression(expression)
if (isAssertReadAccess(expression)) found = true
}
}
})
return found
}
}
@@ -38,7 +38,6 @@ class DefaultLockReqsPatterns : LockReqsPatterns {
"com.intellij.util.concurrency.annotations.RequiresEdt" to LockType.EDT,
"com.intellij.util.concurrency.annotations.RequiresBackgroundThread" to LockType.BGT,
"com.intellij.util.concurrency.annotations.RequiresReadLockAbsence" to LockType.NO_READ,
"org.jetbrains.annotations.RequiresEdt" to LockType.EDT
)
override val edtRequiredPackages: Set<String> = setOf(
@@ -1,7 +1,6 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.threadingModelHelper
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
@@ -9,7 +8,7 @@ import com.intellij.ui.content.ContentFactory
import org.jetbrains.idea.devkit.threadingModelHelper.ui.LockReqsToolWindow
import org.jetbrains.jewel.bridge.JewelComposePanel
class LockReqsToolWindowFactoryCompose : ToolWindowFactory, DumbAware {
class LockReqsToolWindowFactory : ToolWindowFactory {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val contentFactory = ContentFactory.getInstance()
val panel = JewelComposePanel { LockReqsToolWindow(project) }
@@ -1,37 +0,0 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.threadingModelHelper
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.wm.ToolWindow
import com.intellij.openapi.wm.ToolWindowFactory
import com.intellij.ui.components.JBScrollPane
import com.intellij.ui.table.JBTable
import javax.swing.JPanel
import javax.swing.table.DefaultTableModel
import java.awt.BorderLayout
@Deprecated("Use LockReqsToolWindowFactoryCompose instead")
class LockReqsToolWindowFactorySwing : ToolWindowFactory {
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
val service = project.service<LockReqsService>()
val tableModel = DefaultTableModel(arrayOf("Execution Path"), 0)
val panel = JPanel(BorderLayout())
val table = JBTable(tableModel)
val scrollPane = JBScrollPane(table)
panel.add(scrollPane)
// service.onResultsUpdated = { loadData(service.currentResult.paths.map{it.pathString}, tableModel) }
val factory = toolWindow.contentManager.factory
val content = factory.createContent(panel, null, false)
toolWindow.contentManager.addContent(content)
}
private fun loadData(results: List<String>, tableModel: DefaultTableModel) {
tableModel.rowCount = 0
results.forEach { tableModel.addRow(arrayOf(it)) }
}
}
@@ -33,7 +33,7 @@ internal fun LockReqsToolWindow(project: Project) {
Column(
modifier = Modifier.fillMaxSize()
) {
val paths = analysisResult?.paths ?: emptyList()
val paths = analysisResult?.paths?.toList() ?: emptyList()
ToolWindowHeader(
pathsCount = paths.size,
@@ -1,8 +1,5 @@
import testutils.RequiresReadLock;
import testutils.ExpectedPath
import com.intellij.util.concurrency.annotations.RequiresReadLock;
@ExpectedPath("AnnotationInChain.testMethod -> AnnotationInChain.intermediateMethod -> " +
"AnnotationInChain.targetMethod -> @RequiresReadLock")
class AnnotationInChain {
void testMethod() {
intermediateMethod();
@@ -1,8 +1,6 @@
import testutils.ThreadingAssertions;
import testutils.ExpectedPath
import com.intellij.util.concurrency.ThreadingAssertions;
@ExpectedPath("AnnotationInChain.testMethod -> @RequiresReadLock")
class AnnotationInChain {
class AssertionInNestedBlock {
void testMethod() {
if (true) {
for (int i = 0; i < 10; i++) {
@@ -1,12 +1,9 @@
import testutils.RequiresReadLock;
import testutils.ThreadingAssertions;
import test.ExpectedPath
import com.intellij.util.concurrency.ThreadingAssertions;
import com.intellij.util.concurrency.annotations.RequiresWriteLock;
@ExpectedPath("BothAnnotationAndAssertion.testMethod -> @RequiresReadLock")
@ExpectedPath("BothAnnotationAndAssertion.testMethod -> ThreadingAssertions.assertReadAccess()")
class BothAnnotationAndAssertion {
@RequiresReadLock
@RequiresWriteLock
void testMethod() {
ThreadingAssertions.assertReadAccess();
ThreadingAssertions.assertBackgroundThread();
}
}
@@ -1,7 +1,6 @@
import testutils.RequiresReadLock;
import testutils.ExpectedPath
import com.intellij.util.concurrency.annotations.RequiresReadLock;
import com.intellij.util.concurrency.ThreadingAssertions;
@ExpectedPath("CyclicRecursiveCalls.testMethod -> CyclicRecursiveCalls.methodB -> @RequiresReadLock")
class CyclicRecursiveCalls {
void testMethod() {
methodB();
@@ -0,0 +1,24 @@
import com.intellij.util.concurrency.annotations.RequiresEdt;
import com.intellij.util.concurrency.ThreadingAssertions;
class DifferentClassesMethods {
void testMethod() {
Helper helper = new Helper();
helper.helperMethod();
}
class Helper {
void helperMethod() {
Service service = new Service();
service.serviceMethod();
}
}
class Service {
@RequiresEdt
void serviceMethod() {
ThreadingAssertions.assertWriteAccess();
}
}
}
@@ -0,0 +1,18 @@
import javax.swing.JPanel;
import javax.swing.JButton;
public class EDTSwingComponent {
public void testMethod() {
JPanel panel = new JPanel();
panel.setVisible(true); // Requires EDT
panel.repaint(); // Safe method - no requirement
setupButton();
}
private void setupButton() {
JButton button = new JButton("Click");
button.setText("Updated");
button.revalidate();
}
}
@@ -1,9 +1,7 @@
import testutils.ThreadingAssertions;
import testutils.ExpectedPath
import com.intellij.util.concurrency.ThreadingAssertions;
import java.util.Arrays;
import java.util.List;
@ExpectedPath("LambdaWithMethodReference.testMethod -> LambdaWithMethodReference.processItem -> ThreadingAssertions.assertReadAccess()")
class LambdaWithMethodReference {
void testMethod() {
List<String> items = Arrays.asList("a", "b", "c");
@@ -1,4 +1,3 @@
package test;
public class C {
public void testMessageBusDetection() {
com.intellij.util.messages.MessageBus mb = null;
@@ -1,27 +0,0 @@
import testutils.RequiresReadLock;
import testutils.ThreadingAssertions;
import testutils.ExpectedPath;
@ExpectedPath("MethodsInDifferentClasses.testMethod -> Helper.helperMethod -> Service.serviceMethod -> @RequiresReadLock")
@ExpectedPath("MethodsInDifferentClasses.testMethod -> Helper.helperMethod -> Service.serviceMethod -> ThreadingAssertions.assertReadAccess()")
class MethodsInDifferentClasses {
void testMethod() {
Helper helper = new Helper();
helper.helperMethod();
}
class Helper {
void helperMethod() {
Service service = new Service();
service.serviceMethod();
}
}
class Service {
@RequiresReadLock
void serviceMethod() {
ThreadingAssertions.assertReadAccess();
}
}
}
@@ -1,7 +1,5 @@
import testutils.ThreadingAssertions;
import testutils.ExpectedPath
import com.intellij.util.concurrency.ThreadingAssertions;
@ExpectedPath("MultipleAssertionsInMethod.testMethod -> ThreadingAssertions.assertReadAccess()")
class MultipleAssertionsInMethod {
void testMethod() {
ThreadingAssertions.assertReadAccess();
@@ -1,20 +0,0 @@
package test;
import com.intellij.util.concurrency.annotations.RequiresReadLock;
public class PolymorphicTest {
public void testMethod() {
Base base = new Derived();
base.polymorphicMethod();
}
}
abstract class Base {
public abstract void polymorphicMethod();
}
class Derived extends Base {
@Override
@RequiresReadLock
public void polymorphicMethod() {}
}
@@ -1,11 +0,0 @@
package test;
import javax.swing.JPanel;
public class SwingTest {
public void testMethod() {
JPanel panel = new JPanel();
panel.paint();
panel.repaint();
}
}
@@ -0,0 +1,35 @@
import com.intellij.util.concurrency.annotations.RequiresReadLock;
import com.intellij.util.concurrency.ThreadingAssertions;
public class SubtypingPolymorphism {
public void testMethod() {
Service[] services = {new FileService(), new NetworkService(), new DatabaseService()};
for (Service service : services) {
service.execute();
}
}
}
interface Service {
void execute();
}
class FileService implements Service {
@Override
@RequiresReadLock
public void execute() { }
}
class UIService implements Service {
@Override
public void execute() {
ThreadingAssertions.assertEventDispatchThread();
}
}
class DBService implements Service {
@Override
public void execute() {
ThreadingAssertions.assertReadAccess();
}
}
@@ -15,68 +15,120 @@ class LockReqsUnitTest : BasePlatformTestCase() {
override fun setUp() {
super.setUp()
analyzer = LockReqsAnalyzer()
myFixture.addFileToProject("mock/RequiresReadLock.java", """
package mock;
myFixture.addFileToProject("com/intellij/util/concurrency/annotations.java", """
package com.intellij.util.concurrency.annotations;
public @interface RequiresReadLock {}
public @interface RequiresWriteLock {}
public @interface RequiresEdt {}
public @interface RequiresBackgroundThread {}
public @interface RequiresReadLockAbsence {}
""".trimIndent())
myFixture.addFileToProject("mock/ThreadingAssertions.java", """
package mock;
myFixture.addFileToProject("com/intellij/util/concurrency/ThreadingAssertions.java", """
package com.intellij.util.concurrency;
public class ThreadingAssertions {
public static void assertReadAccess() {}
public static void assertWriteAccess() {}
public static void assertWriteIntentReadAccess() {}
public static void assertEventDispatchThread() {}
public static void assertBackgroundThread() {}
}
""".trimIndent())
myFixture.addFileToProject("mock/MessageBus.java", """
package com.intellij.util.concurrency;
public interface MessageBus {
<L> L syncPublisher(Class<L> topic);
}
""".trimIndent())
}
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "threadingModelHelper/"
/*
fun testNoLockRequirements() {
doTest("NoLockRequirements", "testMethod", emptyList())
}
fun testAnnotationInChain() {
doTest()
val result = doTest("NoLockRequirements", "testMethod")
val actualPaths = formatResult(result)
assertTrue(actualPaths.isEmpty())
}
fun testAssertionInNestedBlock() {
doTest()
val result = doTest("AssertionInNestedBlock", "testMethod")
val expectedPaths = listOf("AssertionInNestedBlock.testMethod => READ.ASSERTION")
val actualPaths = formatResult(result)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testAnnotationInChain() {
val result = doTest("AnnotationInChain", "testMethod")
val expectedPaths = listOf("AnnotationInChain.testMethod -> AnnotationInChain.intermediateMethod" +
" -> AnnotationInChain.targetMethod => READ.ANNOTATION")
val actualPaths = formatResult(result)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testBothAnnotationAndAssertion() {
doTest()
val result = doTest("BothAnnotationAndAssertion", "testMethod")
val expectedPaths = listOf("BothAnnotationAndAssertion.testMethod => WRITE.ANNOTATION",
"BothAnnotationAndAssertion.testMethod => BGT.ASSERTION")
val actualPaths = formatResult(result)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testCyclicRecursiveCalls() {
doTest()
val result = doTest("CyclicRecursiveCalls", "testMethod")
val expectedPaths = listOf("CyclicRecursiveCalls.testMethod -> CyclicRecursiveCalls.methodB => READ.ANNOTATION")
val actualPaths = formatResult(result)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testMethodsInDifferentClasses() {
doTest()
fun testDifferentClassesMethods() {
val result = doTest("DifferentClassesMethods", "testMethod")
val expectedPaths = listOf("DifferentClassesMethods.testMethod -> Helper.helperMethod -> Service.serviceMethod => EDT.ANNOTATION",
"DifferentClassesMethods.testMethod -> Helper.helperMethod -> Service.serviceMethod => WRITE.ASSERTION")
val actualPaths = formatResult(result)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testMultipleAssertionsInMethod() {
doTest()
val result = doTest("MultipleAssertionsInMethod", "testMethod")
val expectedPaths = listOf("MultipleAssertionsInMethod.testMethod => READ.ASSERTION")
val actualPaths = formatResult(result)
assertEquals(1, actualPaths.size)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testLambdaWithMethodReference() {
doTest()
}*/
private fun doTest(className: String, methodName: String, expectedPaths: List<String>) {
fun testLambdaWithMethodReference() {
val result = doTest("LambdaWithMethodReference", "testMethod")
val expectedPaths = listOf("LambdaWithMethodReference.testMethod -> LambdaWithMethodReference.processItem => READ.ASSERTION")
val actualPaths = formatResult(result)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
fun testSubtypingPolymorphism() {
val result = doTest("SubtypingPolymorphism", "testMethod")
val expectedPaths = listOf(
"SubtypingPolymorphism.testMethod -> FileService.execute => READ.ANNOTATION",
"SubtypingPolymorphism.testMethod -> UIService.execute => EDT.ASSERTION",
"SubtypingPolymorphism.testMethod -> DBService.execute => READ.ASSERTION"
)
val actualPaths = formatResult(result)
assertEquals(expectedPaths.sorted(), actualPaths.sorted())
}
private fun doTest(className: String, methodName: String): AnalysisResult {
val fileName = "${getTestName(false)}.java"
val psiJavaFile = myFixture.configureByFile(fileName) as PsiJavaFile
val targetClass = psiJavaFile.classes.find { it.name == className } ?: error("Could not find class $className")
val targetMethod = targetClass.methods.find { it.name == className } ?: error("Could not find method $methodName")
val targetMethod = targetClass.methods.find { it.name == methodName } ?: error("Could not find method $methodName")
return analyzer.analyzeMethod(targetMethod)
}
val result = analyzer.analyzeMethod(targetMethod)
private fun formatResult(result: AnalysisResult): List<String> {
val actualPaths = result.paths.map { path ->
buildString {
path.methodChain.joinToString(separator = " -> ", postfix = " -> ") {
"${it.method.containingClass?.name}.${it.method.name}"
}
"@${path.lockRequirement.lockType}"
}
val chain = path.methodChain.joinToString(" -> ") { "${it.method.containingClass?.name}.${it.method.name}" }
val requirement = "${path.lockRequirement.lockType.name}.${path.lockRequirement.requirementReason.name}"
"$chain => $requirement"
}
assertEquals(expectedPaths, actualPaths)
return actualPaths
}
}