Skip to content

Commit f752745

Browse files
author
Steve Yegge
committed
some minor refactoring of doc sync tests
1 parent 5e32b07 commit f752745

File tree

2 files changed

+271
-10
lines changed

2 files changed

+271
-10
lines changed

src/integrationTest/kotlin/com/sourcegraph/cody/DocumentSynchronizationTest.kt

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ class DocumentSynchronizationTest : DocumentSynchronizationTestFixture() {
2828
"""
2929

3030
runDocumentSynchronizationTest(beforeContent, expectedContent) { editor: Editor ->
31-
val document = editor.document
32-
document.insertString(editor.caretModel.offset, "!")
31+
editor.document.insertString(editor.caretModel.offset, "!")
3332
}
3433
}
3534

@@ -50,9 +49,8 @@ class DocumentSynchronizationTest : DocumentSynchronizationTestFixture() {
5049
"""
5150

5251
runDocumentSynchronizationTest(beforeContent, expectedContent) { editor: Editor ->
53-
val document = editor.document
5452
val offset = editor.caretModel.offset
55-
document.deleteString(offset, offset + 1)
53+
editor.document.deleteString(offset, offset + 1)
5654
}
5755
}
5856

@@ -73,9 +71,8 @@ class DocumentSynchronizationTest : DocumentSynchronizationTestFixture() {
7371
"""
7472

7573
runDocumentSynchronizationTest(beforeContent, expectedContent) { editor: Editor ->
76-
val document = editor.document
7774
val offset = editor.caretModel.offset
78-
document.deleteString(offset, offset + "console.log".length)
75+
editor.document.deleteString(offset, offset + "console.log".length)
7976
}
8077
}
8178

@@ -96,9 +93,8 @@ class DocumentSynchronizationTest : DocumentSynchronizationTestFixture() {
9693
"""
9794

9895
runDocumentSynchronizationTest(beforeContent, expectedContent) { editor: Editor ->
99-
val document = editor.document
10096
val offset = editor.caretModel.offset
101-
document.replaceString(offset, offset + "System.out.println".length, "console.log")
97+
editor.document.replaceString(offset, offset + "System.out.println".length, "console.log")
10298
}
10399
}
104100

@@ -248,8 +244,7 @@ class DocumentSynchronizationTest : DocumentSynchronizationTestFixture() {
248244
"""
249245

250246
runDocumentSynchronizationTest(beforeContent, expectedContent) { editor: Editor ->
251-
val offset = editor.caretModel.offset
252-
editor.document.insertString(offset, "!🎉🎂\n 🥳🎈")
247+
editor.document.insertString(editor.caretModel.offset, "!🎉🎂\n 🥳🎈")
253248
}
254249
}
255250

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
package com.sourcegraph.cody.util
2+
3+
import com.intellij.ide.lightEdit.LightEdit
4+
import com.intellij.openapi.actionSystem.ActionManager
5+
import com.intellij.openapi.actionSystem.AnActionEvent
6+
import com.intellij.openapi.actionSystem.DataContext
7+
import com.intellij.openapi.application.ApplicationManager
8+
import com.intellij.openapi.command.WriteCommandAction
9+
import com.intellij.openapi.diagnostic.Logger
10+
import com.intellij.openapi.editor.Editor
11+
import com.intellij.openapi.editor.ex.EditorEx
12+
import com.intellij.openapi.fileEditor.FileDocumentManager
13+
import com.intellij.openapi.project.DumbService
14+
import com.intellij.openapi.project.Project
15+
import com.intellij.testFramework.EditorTestUtil
16+
import com.intellij.testFramework.PlatformTestUtil
17+
import com.intellij.testFramework.fixtures.BasePlatformTestCase
18+
import com.intellij.testFramework.runInEdtAndWait
19+
import com.intellij.util.messages.Topic
20+
import com.sourcegraph.cody.agent.CodyAgentService
21+
import com.sourcegraph.cody.config.CodyPersistentAccountsHost
22+
import com.sourcegraph.cody.config.SourcegraphServerPath
23+
import com.sourcegraph.cody.edit.CodyInlineEditActionNotifier
24+
import com.sourcegraph.cody.edit.FixupService
25+
import com.sourcegraph.cody.edit.sessions.FixupSession
26+
import com.sourcegraph.config.ConfigUtil
27+
import java.io.File
28+
import java.nio.file.Paths
29+
import java.util.concurrent.CompletableFuture
30+
import java.util.concurrent.TimeUnit
31+
import java.util.regex.Pattern
32+
33+
open class CodyIntegrationTextFixture : BasePlatformTestCase() {
34+
private val logger = Logger.getInstance(CodyIntegrationTextFixture::class.java)
35+
36+
override fun setUp() {
37+
super.setUp()
38+
configureFixture()
39+
checkInitialConditions()
40+
myProject = project
41+
}
42+
43+
override fun tearDown() {
44+
try {
45+
assertNotNull(project)
46+
FixupService.getInstance(project).getActiveSession()?.apply {
47+
try {
48+
dispose()
49+
} catch (x: Exception) {
50+
logger.warn("Error shutting down session", x)
51+
}
52+
}
53+
} finally {
54+
super.tearDown()
55+
}
56+
}
57+
58+
private fun configureFixture() {
59+
// If you don't specify this system property with this setting when running the tests,
60+
// the tests will fail, because IntelliJ will run them from the EDT, which can't block.
61+
// Setting this property invokes the tests from an executor pool thread, which lets us
62+
// block/wait on potentially long-running operations during the integration test.
63+
val policy = System.getProperty("idea.test.execution.policy")
64+
assertTrue(policy == "com.sourcegraph.cody.test.NonEdtIdeaTestExecutionPolicy")
65+
66+
// This is wherever src/integrationTest/resources is on the box running the tests.
67+
val testResourcesDir = File(System.getProperty("test.resources.dir"))
68+
assertTrue(testResourcesDir.exists())
69+
70+
// During test runs this is set by IntelliJ to a private temp folder.
71+
// We pass it to the Agent during initialization.
72+
val workspaceRootUri = ConfigUtil.getWorkspaceRootPath(project)
73+
74+
// We copy the test resources there manually, bypassing Gradle, which is picky.
75+
val testDataPath = Paths.get(workspaceRootUri.toString(), "src/").toFile()
76+
testResourcesDir.copyRecursively(testDataPath, overwrite = true)
77+
78+
// This useful setting lets us tell the fixture to look where we copied them.
79+
myFixture.testDataPath = testDataPath.path
80+
81+
// The file we pass to configureByFile must be relative to testDataPath.
82+
val projectFile = "testProjects/documentCode/src/main/java/Foo.java"
83+
val sourcePath = Paths.get(testDataPath.path, projectFile).toString()
84+
assertTrue(File(sourcePath).exists())
85+
myFixture.configureByFile(projectFile)
86+
87+
initCredentialsAndAgent()
88+
initCaretPosition()
89+
}
90+
91+
// Ideally we should call this method only once per recording session, but since we need a
92+
// `project` to be present it is currently hard to do with Junit 4.
93+
// Methods there are mostly idempotent though, so calling again for every test case should not
94+
// change anything.
95+
private fun initCredentialsAndAgent() {
96+
val credentials = TestingCredentials.dotcom
97+
CodyPersistentAccountsHost(project)
98+
.addAccount(
99+
SourcegraphServerPath.from(credentials.serverEndpoint, ""),
100+
login = "test_user",
101+
displayName = "Test User",
102+
token = credentials.token ?: credentials.redactedToken,
103+
id = "random-unique-testing-id-1337")
104+
105+
assertNotNull(
106+
"Unable to start agent in a timely fashion!",
107+
CodyAgentService.getInstance(project)
108+
.startAgent(project)
109+
.completeOnTimeout(null, ASYNC_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
110+
.get())
111+
}
112+
113+
private fun checkInitialConditions() {
114+
val project = myFixture.project
115+
116+
// Check if the project is in dumb mode
117+
val isDumbMode = DumbService.getInstance(project).isDumb
118+
assertFalse("Project should not be in dumb mode", isDumbMode)
119+
120+
// Check if the project is in LightEdit mode
121+
val isLightEditMode = LightEdit.owns(project)
122+
assertFalse("Project should not be in LightEdit mode", isLightEditMode)
123+
124+
// Check the initial state of the action's presentation
125+
val action = ActionManager.getInstance().getAction("cody.documentCodeAction")
126+
val event =
127+
AnActionEvent.createFromAnAction(action, null, "", createEditorContext(myFixture.editor))
128+
action.update(event)
129+
val presentation = event.presentation
130+
assertTrue("Action should be enabled", presentation.isEnabled)
131+
assertTrue("Action should be visible", presentation.isVisible)
132+
}
133+
134+
private fun createEditorContext(editor: Editor): DataContext {
135+
return (editor as? EditorEx)?.dataContext ?: DataContext.EMPTY_CONTEXT
136+
}
137+
138+
// This provides a crude mechanism for specifying the caret position in the test file.
139+
private fun initCaretPosition() {
140+
runInEdtAndWait {
141+
val virtualFile = myFixture.file.virtualFile
142+
val document = FileDocumentManager.getInstance().getDocument(virtualFile)!!
143+
val caretToken = "[[caret]]"
144+
val caretIndex = document.text.indexOf(caretToken)
145+
146+
if (caretIndex != -1) { // Remove caret token from doc
147+
WriteCommandAction.runWriteCommandAction(project) {
148+
document.deleteString(caretIndex, caretIndex + caretToken.length)
149+
}
150+
// Place the caret at the position where the token was found.
151+
myFixture.editor.caretModel.moveToOffset(caretIndex)
152+
// myFixture.editor.selectionModel.setSelection(caretIndex, caretIndex)
153+
} else {
154+
initSelectionRange()
155+
}
156+
}
157+
}
158+
159+
// Provides a mechanism to specify the selection range via [[start]] and [[end]].
160+
// The tokens are removed and the range is selected, notifying the Agent.
161+
private fun initSelectionRange() {
162+
runInEdtAndWait {
163+
val virtualFile = myFixture.file.virtualFile
164+
val document = FileDocumentManager.getInstance().getDocument(virtualFile)!!
165+
val startToken = "[[start]]"
166+
val endToken = "[[end]]"
167+
val start = document.text.indexOf(startToken)
168+
val end = document.text.indexOf(endToken)
169+
// Remove the tokens from the document.
170+
if (start != -1 && end != -1) {
171+
ApplicationManager.getApplication().runWriteAction {
172+
document.deleteString(start, start + startToken.length)
173+
document.deleteString(end, end + endToken.length)
174+
}
175+
myFixture.editor.selectionModel.setSelection(start, end)
176+
} else {
177+
logger.warn("No caret or selection range specified in test file.")
178+
}
179+
}
180+
}
181+
182+
private fun triggerAction(actionId: String) {
183+
runInEdtAndWait {
184+
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()
185+
EditorTestUtil.executeAction(myFixture.editor, actionId)
186+
}
187+
}
188+
189+
protected fun activeSession(): FixupSession {
190+
assertActiveSession()
191+
return FixupService.getInstance(project).getActiveSession()!!
192+
}
193+
194+
protected fun assertNoInlayShown() {
195+
runInEdtAndWait {
196+
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()
197+
assertFalse(
198+
"Lens group inlay should NOT be displayed",
199+
myFixture.editor.inlayModel.hasBlockElements())
200+
}
201+
}
202+
203+
protected fun assertInlayIsShown() {
204+
runInEdtAndWait {
205+
PlatformTestUtil.dispatchAllEventsInIdeEventQueue()
206+
assertTrue(
207+
"Lens group inlay should be displayed", myFixture.editor.inlayModel.hasBlockElements())
208+
}
209+
}
210+
211+
protected fun assertNoActiveSession() {
212+
assertNull(
213+
"NO active session was expected", FixupService.getInstance(project).getActiveSession())
214+
}
215+
216+
protected fun assertActiveSession() {
217+
assertNotNull(
218+
"Active session was expected", FixupService.getInstance(project).getActiveSession())
219+
}
220+
221+
protected fun runAndWaitForNotifications(
222+
actionId: String,
223+
vararg topic: Topic<CodyInlineEditActionNotifier>
224+
) {
225+
val futures = topic.associateWith { subscribeToTopic(it) }
226+
triggerAction(actionId)
227+
futures.forEach { (t, f) ->
228+
try {
229+
f.get()
230+
} catch (e: Exception) {
231+
assertTrue(
232+
"Error while awaiting ${t.displayName} notification: ${e.localizedMessage}", false)
233+
}
234+
}
235+
}
236+
237+
// Returns a future that completes when the topic is published.
238+
private fun subscribeToTopic(
239+
topic: Topic<CodyInlineEditActionNotifier>,
240+
): CompletableFuture<Void> {
241+
val future = CompletableFuture<Void>().orTimeout(ASYNC_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS)
242+
project.messageBus
243+
.connect()
244+
.subscribe(
245+
topic,
246+
object : CodyInlineEditActionNotifier {
247+
override fun afterAction() {
248+
logger.warn("Notification sent for topic '${topic.displayName}'")
249+
future.complete(null)
250+
}
251+
})
252+
logger.warn("Subscribed to topic: $topic")
253+
return future
254+
}
255+
256+
protected fun hasJavadocComment(text: String): Boolean {
257+
// TODO: Check for the exact contents once they are frozen.
258+
val javadocPattern = Pattern.compile("/\\*\\*.*?\\*/", Pattern.DOTALL)
259+
return javadocPattern.matcher(text).find()
260+
}
261+
262+
companion object {
263+
const val ASYNC_WAIT_TIMEOUT_SECONDS = 10L
264+
var myProject: Project? = null
265+
}
266+
}

0 commit comments

Comments
 (0)