Skip to content

Commit a718b80

Browse files
committed
feat(test): add TestAgentObserver for test failure handling #259
Introduce TestAgentObserver to handle test failures by sending error details to the chat window. Refactor AutoDevToolWindowFactory to remove test observer logic and delegate it to the new observer. Add AgentObserver extension point and register TestAgentObserver in the application lifecycle.
1 parent 9fdcb82 commit a718b80

File tree

6 files changed

+103
-63
lines changed

6 files changed

+103
-63
lines changed

core/src/223/main/resources/META-INF/autodev-core.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,9 +227,12 @@
227227

228228
<!-- mcp -->
229229
<extensionPoint qualifiedName="cc.unitmesh.mcpTool"
230-
interface="cc.unitmesh.devti.mcp.McpTool"
230+
interface="cc.unitmesh.devti.mcp.host.McpTool"
231231
dynamic="true"/>
232232

233+
<extensionPoint qualifiedName="cc.unitmesh.agentObserver"
234+
interface="cc.unitmesh.devti.provider.observer.AgentObserver"
235+
dynamic="true"/>
233236
</extensionPoints>
234237

235238
<applicationListeners>

core/src/233/main/kotlin/cc/unitmesh/devti/update/AutoDevUpdateStartupActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cc.unitmesh.devti.update
22

33
import cc.unitmesh.devti.inline.AutoDevInlineChatProvider
4+
import cc.unitmesh.devti.provider.observer.AgentObserver
45
import com.intellij.openapi.application.ApplicationManager
56
import com.intellij.openapi.project.Project
67
import com.intellij.openapi.startup.ProjectActivity
@@ -12,5 +13,6 @@ class AutoDevUpdateStartupActivity : ProjectActivity {
1213
if (ApplicationManager.getApplication().isUnitTestMode) return
1314

1415
AutoDevInlineChatProvider.addListener(project)
16+
AgentObserver.register(project)
1517
}
1618
}

core/src/233/main/resources/META-INF/autodev-core.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,10 @@
229229
<extensionPoint qualifiedName="cc.unitmesh.mcpTool"
230230
interface="cc.unitmesh.devti.mcp.host.McpTool"
231231
dynamic="true"/>
232+
233+
<extensionPoint qualifiedName="cc.unitmesh.agentObserver"
234+
interface="cc.unitmesh.devti.provider.observer.AgentObserver"
235+
dynamic="true"/>
232236
</extensionPoints>
233237

234238
<applicationListeners>
@@ -271,6 +275,8 @@
271275
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.knowledge.HistoryFunctionProvider"/>
272276
<toolchainFunctionProvider implementation="cc.unitmesh.devti.bridge.knowledge.KnowledgeFunctionProvider"/>
273277
<toolchainFunctionProvider implementation="cc.unitmesh.devti.mcp.client.McpFunctionProvider"/>
278+
279+
<agentObserver implementation="cc.unitmesh.devti.observer.TestAgentObserver" />
274280
</extensions>
275281

276282
<actions>

core/src/main/kotlin/cc/unitmesh/devti/gui/AutoDevToolWindowFactory.kt

Lines changed: 5 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,32 @@ import cc.unitmesh.devti.gui.chat.ChatCodingService
66
import cc.unitmesh.devti.gui.chat.NormalChatCodingPanel
77
import cc.unitmesh.devti.gui.chat.message.ChatActionType
88
import cc.unitmesh.devti.inline.AutoDevInlineChatProvider
9+
import cc.unitmesh.devti.provider.observer.AgentObserver
910
import cc.unitmesh.devti.settings.locale.LanguageChangedCallback
1011
import cc.unitmesh.devti.settings.locale.LanguageChangedCallback.componentStateChanged
1112
import cc.unitmesh.devti.sketch.SketchToolWindow
12-
import cc.unitmesh.devti.util.relativePath
13-
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
14-
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener
15-
import com.intellij.execution.testframework.sm.runner.SMTestProxy
16-
import com.intellij.openapi.Disposable
1713
import com.intellij.openapi.actionSystem.ex.ActionUtil
1814
import com.intellij.openapi.application.ApplicationManager
19-
import com.intellij.openapi.application.runInEdt
20-
import com.intellij.openapi.application.runReadAction
21-
import com.intellij.openapi.diagnostic.logger
2215
import com.intellij.openapi.project.DumbAware
2316
import com.intellij.openapi.project.Project
2417
import com.intellij.openapi.wm.ToolWindow
2518
import com.intellij.openapi.wm.ToolWindowFactory
2619
import com.intellij.openapi.wm.ToolWindowManager
27-
import com.intellij.psi.search.ProjectScope
2820
import com.intellij.ui.content.Content
2921
import com.intellij.ui.content.ContentFactory
30-
import com.intellij.util.messages.MessageBusConnection
3122

3223
private const val NORMAL_CHAT = "AutoDev Chat"
3324
private const val SKETCH_TITLE = "Sketch"
3425
private const val BRIDGE_TITLE = "Bridge"
3526
private const val CHAT_KEY = "autodev.chat"
3627

37-
class AutoDevToolWindowFactory : ToolWindowFactory, DumbAware, Disposable {
28+
class AutoDevToolWindowFactory : ToolWindowFactory, DumbAware {
3829
object Util {
3930
const val id = "AutoDev"
4031
}
4132

42-
private var connection: MessageBusConnection? = null
43-
4433
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
45-
initInlineChatForIdea223(project)
46-
initObservers(project)
34+
initForIdea223(project)
4735
ApplicationManager.getApplication().invokeLater {
4836
val normalChatTitle = AutoDevBundle.messageWithLanguage(CHAT_KEY, LanguageChangedCallback.language)
4937
val normalChatPanel =
@@ -67,57 +55,12 @@ class AutoDevToolWindowFactory : ToolWindowFactory, DumbAware, Disposable {
6755
}
6856
}
6957

70-
private fun initObservers(project: Project) {
71-
connection = project.messageBus.connect()
72-
val searchScope = ProjectScope.getProjectScope(project)
73-
connection?.subscribe(SMTRunnerEventsListener.TEST_STATUS, object : SMTRunnerEventsAdapter() {
74-
override fun onSuiteFinished(suite: SMTestProxy, nodeId: String?) {
75-
logger<AutoDevToolWindowFactory>().info(suite.toString())
76-
}
77-
78-
override fun onTestFailed(test: SMTestProxy) {
79-
val sourceCode = test.getLocation(project, searchScope)
80-
runInEdt {
81-
sendToChatWindow(project, ChatActionType.CHAT) { contentPanel, _ ->
82-
val psiElement = sourceCode?.psiElement
83-
val language = psiElement?.language?.displayName ?: ""
84-
val filepath = psiElement?.containingFile?.virtualFile?.relativePath(project) ?: ""
85-
val code = runReadAction { psiElement?.text ?: "" }
86-
contentPanel.setInput(
87-
"""Help me fix follow test issue:
88-
| ErrorMessage:
89-
|```
90-
|${test.errorMessage}
91-
|```
92-
|stacktrace details:
93-
|${test.stacktrace}
94-
|
95-
|// filepath: $filepath
96-
|origin code:
97-
|```$language
98-
|$code
99-
|```
100-
|""".trimMargin()
101-
)
102-
}
103-
}
104-
}
105-
106-
override fun onTestFinished(test: SMTestProxy, nodeId: String?) {
107-
logger<AutoDevToolWindowFactory>().info(nodeId)
108-
}
109-
})
110-
}
111-
112-
override fun dispose() {
113-
connection?.disconnect()
114-
}
115-
11658
/**
11759
* for idea 223 (aka 2022.3) which don't have [com.intellij.openapi.startup.ProjectActivity]
11860
*/
119-
private fun initInlineChatForIdea223(project: Project) {
61+
private fun initForIdea223(project: Project) {
12062
AutoDevInlineChatProvider.addListener(project)
63+
AgentObserver.register(project)
12164
}
12265

12366
override fun init(toolWindow: ToolWindow) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cc.unitmesh.devti.observer
2+
3+
import cc.unitmesh.devti.gui.AutoDevToolWindowFactory
4+
import cc.unitmesh.devti.gui.chat.message.ChatActionType
5+
import cc.unitmesh.devti.gui.sendToChatWindow
6+
import cc.unitmesh.devti.provider.observer.AgentObserver
7+
import cc.unitmesh.devti.util.relativePath
8+
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsAdapter
9+
import com.intellij.execution.testframework.sm.runner.SMTRunnerEventsListener.TEST_STATUS
10+
import com.intellij.execution.testframework.sm.runner.SMTestProxy
11+
import com.intellij.openapi.Disposable
12+
import com.intellij.openapi.application.runInEdt
13+
import com.intellij.openapi.application.runReadAction
14+
import com.intellij.openapi.diagnostic.logger
15+
import com.intellij.openapi.project.Project
16+
import com.intellij.openapi.util.NlsSafe
17+
import com.intellij.psi.search.ProjectScope.getProjectScope
18+
import com.intellij.util.messages.MessageBusConnection
19+
20+
class TestAgentObserver : AgentObserver, Disposable {
21+
private var connection: MessageBusConnection? = null
22+
override fun onRegister(project: Project) {
23+
connection = project.messageBus.connect()
24+
val searchScope = getProjectScope(project)
25+
connection?.subscribe(TEST_STATUS, object : SMTRunnerEventsAdapter() {
26+
override fun onSuiteFinished(suite: SMTestProxy, nodeId: String?) {
27+
logger<AutoDevToolWindowFactory>().info(suite.toString())
28+
}
29+
30+
override fun onTestFailed(test: SMTestProxy) {
31+
val sourceCode = test.getLocation(project, searchScope)
32+
runInEdt {
33+
sendToChatWindow(project, ChatActionType.CHAT) { contentPanel, _ ->
34+
val psiElement = sourceCode?.psiElement
35+
val language = psiElement?.language?.displayName ?: ""
36+
val filepath = psiElement?.containingFile?.virtualFile?.relativePath(project) ?: ""
37+
val code = runReadAction<@NlsSafe String> { psiElement?.text ?: "" }
38+
contentPanel.setInput(
39+
"""Help me fix follow test issue:
40+
| ErrorMessage:
41+
|```
42+
|${test.errorMessage}
43+
|```
44+
|stacktrace details:
45+
|${test.stacktrace}
46+
|
47+
|// filepath: $filepath
48+
|origin code:
49+
|```$language
50+
|$code
51+
|```
52+
|""".trimMargin()
53+
)
54+
}
55+
}
56+
}
57+
58+
override fun onTestFinished(test: SMTestProxy, nodeId: String?) {
59+
logger<TestAgentObserver>().info(nodeId)
60+
}
61+
})
62+
}
63+
64+
override fun dispose() {
65+
connection?.disconnect()
66+
}
67+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package cc.unitmesh.devti.provider.observer
2+
3+
import com.intellij.openapi.extensions.ExtensionPointName
4+
import com.intellij.openapi.project.Project
5+
6+
interface AgentObserver {
7+
fun onRegister(project: Project)
8+
9+
companion object {
10+
private val EP_NAME: ExtensionPointName<AgentObserver> =
11+
ExtensionPointName("cc.unitmesh.agentObserver")
12+
13+
fun register(project: Project) {
14+
EP_NAME.extensions.forEach { observer ->
15+
observer.onRegister(project)
16+
}
17+
}
18+
}
19+
}

0 commit comments

Comments
 (0)