Skip to content

Commit c6f8517

Browse files
committed
test: resync copied Scala-plugin testkit, cleanup, move to a separate module
1 parent a639dfc commit c6f8517

84 files changed

Lines changed: 764 additions & 3036 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.scalafmt.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ newlines {
3333
project.excludeFilters = [
3434
"macros/*",
3535
"src/test/scala/intellij/testfixtures/*",
36+
"scala-plugin-testkit/.*",
3637
]
3738

3839
rewrite {

build.sbt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,12 @@ lazy val root =
6363
// using PackagingMethod.Standalone, mirroring the pattern used by the Scala plugin for other test runners
6464
packageAdditionalProjects += zio2TestRunner
6565
)
66-
.dependsOn(macros)
66+
.dependsOn(macros, scalaPluginTestkit % "test->test")
67+
68+
// Vendored copy of the IntelliJ Scala plugin test framework — see scala-plugin-testkit/ORIGIN.md.
69+
lazy val scalaPluginTestkit =
70+
newProject("scala-plugin-testkit", file("scala-plugin-testkit"))
71+
.enablePlugins(SbtIdeaPlugin)
6772

6873
lazy val macros =
6974
newProject("macros", file("macros"))

scala-plugin-testkit/ORIGIN.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# scala-plugin-testkit
2+
3+
- **Source:** https://github.com/JetBrains/intellij-scala
4+
- **Synced from commit:** `345dc52da7` (branch `idea262.release`)
5+
6+
Vendored copy of the IntelliJ **Scala plugin** test framework — the base classes, fixtures and helpers our
7+
inspection/refactoring tests build on. JetBrains does not publish these as a Maven artifact, so they have to be copied
8+
into the repository.
9+
10+
Almost every file is a verbatim copy, but a few carry unavoidable local adaptations, each tagged inline with
11+
`// zio-local`. Do not "clean up" `// zio-local` lines when re-syncing — re-apply them.
12+
13+
Not all files are copied from the upstream, only those that are actually used by our tests.
14+
15+
This module exists purely so the vendored code is **physically isolated** from our own code to make it easier to re-sync
16+
with upstream.
17+
18+
Excluded from scalafmt (see `.scalafmt.conf`) so the files stay byte-comparable with upstream.

src/test/scala/org/jetbrains/plugins/scala/annotator/Message.scala renamed to scala-plugin-testkit/src/test/scala/org/jetbrains/plugins/scala/annotator/Message.scala

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import scala.annotation.nowarn
88
import scala.math.Ordered.orderingToOrdered
99

1010
/**
11-
* See also [[Message2]] for the version with range and text attributes
11+
* See also [[Message2]] for the version with range and text attributes
1212
*/
1313
sealed abstract class Message extends Ordered[Message] {
1414
def element: String
@@ -19,25 +19,24 @@ sealed abstract class Message extends Ordered[Message] {
1919
}
2020

2121
object Message {
22-
case class Info(override val element: String, override val message: String) extends Message
22+
case class Info(override val element: String, override val message: String) extends Message
2323
case class Warning(override val element: String, override val message: String) extends Message
24-
case class Error(override val element: String, override val message: String) extends Message
25-
case class Hint(override val element: String, text: String, override val message: String = "", offsetDelta: Int = 0)
26-
extends Message
24+
case class Error(override val element: String, override val message: String) extends Message
25+
case class Hint(override val element: String, text: String, override val message: String = "", offsetDelta: Int = 0) extends Message
2726

2827
def fromHighlightInfo(info: HighlightInfo, fileText: String): Option[Message] = {
2928
val constructor = HighlightingSeverityToConstructor.get(info.getSeverity)
30-
val range = TextRange.create(info.getStartOffset, info.getEndOffset)
29+
val range = TextRange.create(info.getStartOffset, info.getEndOffset)
3130
constructor.map(_.apply(range.substring(fileText), info.getDescription))
3231
}
3332

3433
@nowarn("cat=deprecation")
3534
val HighlightingSeverityToConstructor: Map[HighlightSeverity, (String, String) => Message] =
3635
Map(
37-
HighlightSeverity.ERROR -> Message.Error.apply,
38-
HighlightSeverity.WARNING -> Message.Warning.apply,
36+
HighlightSeverity.ERROR -> Message.Error.apply,
37+
HighlightSeverity.WARNING -> Message.Warning.apply,
3938
HighlightSeverity.WEAK_WARNING -> Message.Warning.apply,
40-
HighlightSeverity.INFORMATION -> Message.Info.apply,
41-
HighlightSeverity.INFO -> Message.Info.apply
39+
HighlightSeverity.INFORMATION -> Message.Info.apply,
40+
HighlightSeverity.INFO -> Message.Info.apply
4241
)
4342
}

src/test/scala/org/jetbrains/plugins/scala/base/EditorActionTestBase.scala renamed to scala-plugin-testkit/src/test/scala/org/jetbrains/plugins/scala/base/EditorActionTestBase.scala

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
package org.jetbrains.plugins.scala
22
package base
33

4-
import com.intellij.openapi.actionSystem.IdeActions.{
5-
ACTION_EDITOR_BACKSPACE,
6-
ACTION_EDITOR_ENTER,
7-
ACTION_EXPAND_LIVE_TEMPLATE_BY_TAB
8-
}
4+
import com.intellij.openapi.actionSystem.IdeActions.{ACTION_EDITOR_BACKSPACE, ACTION_EDITOR_ENTER, ACTION_EXPAND_LIVE_TEMPLATE_BY_TAB}
95
import com.intellij.openapi.application.ApplicationManager
106
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl
117
import com.intellij.openapi.editor.CaretState
@@ -32,8 +28,8 @@ import scala.util.control.NonFatal
3228
@Category(Array(classOf[EditorTests]))
3329
abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase with ShortCaretMarker {
3430

35-
protected val q: String = "\""
36-
protected val qq: String = "\"\""
31+
protected val q : String = "\""
32+
protected val qq : String = "\"\""
3733
protected val qqq: String = "\"\"\""
3834

3935
private implicit def p: Project = getProject
@@ -42,7 +38,9 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
4238

4339
protected def defaultFileName: String = s"aaa.${fileType.getDefaultExtension}"
4440

45-
protected def configureByText(text: String, fileName: String = defaultFileName, trimText: Boolean = false): Unit = {
41+
protected def configureByText(text: String,
42+
fileName: String = defaultFileName,
43+
trimText: Boolean = false): Unit = {
4644
val (textActual, caretOffsets) = findCaretOffsets(text, trimText)
4745

4846
assertTrue("expected at least one caret", caretOffsets.nonEmpty)
@@ -60,26 +58,22 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
6058
}
6159
val editor = myFixture.getEditor
6260
editor.getCaretModel.moveToOffset(caretOffsets.head)
63-
val caretStates = caretOffsets.map(offset => new CaretState(editor.offsetToLogicalPosition(offset), null, null))
61+
val caretStates = caretOffsets.map { offset => new CaretState(editor.offsetToLogicalPosition(offset), null, null) }
6462
editor.getCaretModel.setCaretsAndSelections(caretStates.asJava)
6563
}
6664

6765
/**
68-
* @param textBefore
69-
* editor text with caret markers before the action
70-
* @param textAfter
71-
* editor text with caret markers after the action
72-
* @param stripTrailingSpacesAfterAction
73-
* whether to trim trailing editor spaces after action perform
74-
* @param testBody
75-
* action to perform with `textBefore`
66+
* @param textBefore editor text with caret markers before the action
67+
* @param textAfter editor text with caret markers after the action
68+
* @param stripTrailingSpacesAfterAction whether to trim trailing editor spaces after action perform
69+
* @param testBody action to perform with `textBefore`
7670
*/
7771
protected def performTest(
7872
textBefore: String,
7973
textAfter: String,
8074
fileName: String = defaultFileName,
8175
trimTestDataText: Boolean = false,
82-
stripTrailingSpacesAfterAction: Boolean = false
76+
stripTrailingSpacesAfterAction: Boolean = false,
8377
)(testBody: () => Unit): Unit = try {
8478
configureByText(textBefore, fileName, trimTestDataText)
8579

@@ -88,9 +82,10 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
8882
val (expectedText, expectedCarets) = findCaretOffsets(textAfter, trimTestDataText)
8983

9084
/**
91-
* Copied from `com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl.checkResult` Replaced inner
92-
* `checkResult` call with `checkCaretOffsets` It allows to see caret positions together with file text directly in
93-
* the diff view of failed test It's more convenient then operating with caret offset (as simple integer value)
85+
* Copied from `com.intellij.testFramework.fixtures.impl.CodeInsightTestFixtureImpl.checkResult`
86+
* Replaced inner `checkResult` call with `checkCaretOffsets`
87+
* It allows to see caret positions together with file text directly in the diff view of failed test
88+
* It's more convenient then operating with caret offset (as simple integer value)
9489
*/
9590
Option(IdeaTestExecutionPolicy.current).foreach(_.beforeCheckResult(getFile))
9691
inWriteCommandAction {
@@ -101,6 +96,7 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
10196
}
10297
} catch {
10398
case cf: org.junit.ComparisonFailure =>
99+
printComparisonFailureRepro(textBefore, cf.getExpected, cf.getActual)
104100
throw cf.withBeforePrefix(textBefore)
105101

106102
case NonFatal(other) =>
@@ -114,25 +110,52 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
114110
protected def performTypingAction(charTyped: Char): Unit =
115111
myFixture.`type`(charTyped)
116112

113+
private def printComparisonFailureRepro(textBefore: String, expectedAfter: String, actualAfter: String): Unit =
114+
System.err.println(
115+
s"""<<<Repro>>>
116+
|<<<Before>>>
117+
|$textBefore
118+
|----------------------------------------------------
119+
|<<<ExpectedAfter>>>
120+
|$expectedAfter
121+
|----------------------------------------------------
122+
|<<<ActualAfter>>>
123+
|$actualAfter
124+
|""".stripMargin
125+
)
126+
117127
protected def performTypingAction(text: String): Unit =
118128
myFixture.`type`(text)
119129

120-
protected def checkGeneratedTextAfterTyping(
121-
textBefore: String,
122-
textAfter: String,
123-
charTyped: Char,
124-
fileName: String = defaultFileName
125-
): Unit =
130+
/**
131+
* Types text character-by-character and commits the editor document after each character.
132+
*
133+
* Why we need this helper for some typing tests:
134+
* - `TypedHandlerDelegate` API does not guarantee PSI is in sync with the document during typing callbacks.
135+
* - `EditorTestFixture.type(String)` types in a tight loop (`type(char)` per character) without explicit commit between characters.
136+
* - Psi commits are asynchronous and coalesced, so in tests PSI can lag behind typed text longer than in production.
137+
*
138+
* In production, human typing cadence usually gives the async commit pipeline enough time to catch up between key presses.
139+
* In tests, typing whole strings too fast can make `file.findElementAt(...)` return `null` in typed handlers and produce
140+
* behavior different from what users see in the IDE.
141+
*
142+
* Without extra "commit" some tests might fail after fixes in the formatter related to SCL-25190
143+
*/
144+
protected def performTypingActionAndCommitEachChar(text: String): Unit =
145+
text.foreach { char: Char =>
146+
performTypingAction(char)
147+
148+
getEditor.getDocument.commit(getProject)
149+
}
150+
151+
protected def checkGeneratedTextAfterTyping(textBefore: String, textAfter: String, charTyped: Char,
152+
fileName: String = defaultFileName): Unit =
126153
performTest(textBefore, textAfter, fileName) { () =>
127154
performTypingAction(charTyped)
128155
}
129156

130-
protected def checkGeneratedTextAfterTypingText(
131-
textBefore: String,
132-
textAfter: String,
133-
textTyped: String,
134-
fileName: String = defaultFileName
135-
): Unit =
157+
protected def checkGeneratedTextAfterTypingText(textBefore: String, textAfter: String, textTyped: String,
158+
fileName: String = defaultFileName): Unit =
136159
performTest(textBefore, textAfter, fileName) { () =>
137160
performTypingAction(textTyped)
138161
}
@@ -144,10 +167,7 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
144167
fileName: String = defaultFileName
145168
): Unit =
146169
performTest(textBefore, textAfter, fileName) { () =>
147-
textTyped.foreach { char: Char =>
148-
performTypingAction(char)
149-
getEditor.getDocument.commit(getProject)
150-
}
170+
performTypingActionAndCommitEachChar(textTyped)
151171
}
152172

153173
protected def performBackspaceAction(): Unit =
@@ -222,13 +242,12 @@ abstract class EditorActionTestBase extends ScalaLightCodeInsightFixtureTestCase
222242
.foldLeft(text)(_.patch(_, CARET, 0))
223243

224244
val expected0 = patchTextWithCarets(expectedText, expectedCarets)
225-
val expected = if (stripTrailingSpaces) doStripTrailingSpaces(expected0) else expected0
245+
val expected = if (stripTrailingSpaces) doStripTrailingSpaces(expected0) else expected0
226246

227-
val actual =
228-
if (expectedCarets.nonEmpty)
229-
patchTextWithCarets(actualText, actualCarets)
230-
else
231-
actualText // if expected text doesn't contain any carets, just don't assert carets positions then
247+
val actual = if (expectedCarets.nonEmpty)
248+
patchTextWithCarets(actualText, actualCarets)
249+
else
250+
actualText //if expected text doesn't contain any carets, just don't assert carets positions then
232251
assertEquals(expected, actual)
233252
}
234253
}

src/test/scala/org/jetbrains/plugins/scala/base/FailableTest.scala renamed to scala-plugin-testkit/src/test/scala/org/jetbrains/plugins/scala/base/FailableTest.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import org.junit.Assert
55
trait FailableTest {
66

77
/**
8-
* A hook to allow tests that are currently failing to pass when they fail and vice versa.
9-
* @return
8+
* A hook to allow tests that are currently failing to pass when they fail and vice versa.
9+
* @return
1010
*/
1111
protected def shouldPass: Boolean = true
1212

13-
protected def assertEqualsFailable(expected: AnyRef, actual: AnyRef): Unit =
13+
protected def assertEqualsFailable(expected: AnyRef, actual: AnyRef): Unit = {
1414
if (shouldPass) Assert.assertEquals(expected, actual)
1515
else Assert.assertNotEquals(expected, actual)
16+
}
1617

1718
protected val failingPassed: String = "Test has passed, but was supposed to fail"
1819
}

src/test/scala/org/jetbrains/plugins/scala/base/InjectableJdk.scala renamed to scala-plugin-testkit/src/test/scala/org/jetbrains/plugins/scala/base/InjectableJdk.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import com.intellij.pom.java.LanguageLevel
55
trait InjectableJdk {
66

77
private var _injectedJdkVersion: Option[LanguageLevel] = None
8-
def injectedJdkVersion: Option[LanguageLevel] = _injectedJdkVersion
9-
def injectedJdkVersion_=(value: LanguageLevel): Unit = _injectedJdkVersion = Some(value)
8+
def injectedJdkVersion: Option[LanguageLevel] = _injectedJdkVersion
9+
def injectedJdkVersion_=(value: LanguageLevel): Unit = _injectedJdkVersion = Some(value)
1010

1111
def defaultJdkVersion: LanguageLevel = InjectableJdk.DefaultJdk
1212

src/test/scala/org/jetbrains/plugins/scala/base/LibrariesOwner.scala renamed to scala-plugin-testkit/src/test/scala/org/jetbrains/plugins/scala/base/LibrariesOwner.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ trait LibrariesOwner {
1414

1515
private lazy val myLoaders = mutable.ListBuffer.empty[LibraryLoader]
1616

17-
protected def setUpLibraries(implicit module: Module): Unit =
17+
protected def setUpLibraries(module: Module): Unit =
1818
librariesLoaders.foreach { loader =>
1919
myLoaders += loader
2020
loader.init(module, version)
2121
}
2222

23-
protected def disposeLibraries(implicit module: Module): Unit = {
24-
myLoaders.foreach(_.clean)
23+
protected def disposeLibraries(module: Module): Unit = {
24+
myLoaders.foreach(_.clean(module))
2525
myLoaders.clear()
2626
}
2727
}

0 commit comments

Comments
 (0)