mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[threading] IJPL-179707: Fix testing suite
GitOrigin-RevId: 55783f7b0c474b390e9372d2dda144c7966642bf
This commit is contained in:
committed by
intellij-monorepo-bot
parent
e38e358490
commit
6c81db53bc
@@ -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
-2
@@ -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
-4
@@ -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();
|
||||
|
||||
+2
-4
@@ -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++) {
|
||||
|
||||
+4
-7
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-3
@@ -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();
|
||||
|
||||
+24
@@ -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
-3
@@ -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
@@ -1,4 +1,3 @@
|
||||
package test;
|
||||
public class C {
|
||||
public void testMessageBusDetection() {
|
||||
com.intellij.util.messages.MessageBus mb = null;
|
||||
|
||||
-27
@@ -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
-3
@@ -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();
|
||||
|
||||
-20
@@ -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() {}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package test;
|
||||
|
||||
import javax.swing.JPanel;
|
||||
|
||||
public class SwingTest {
|
||||
public void testMethod() {
|
||||
JPanel panel = new JPanel();
|
||||
panel.paint();
|
||||
panel.repaint();
|
||||
}
|
||||
}
|
||||
+35
@@ -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();
|
||||
}
|
||||
}
|
||||
+81
-29
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user