From c43aa0311a7502daa84bbd6d0c0eb4e3370ec11b Mon Sep 17 00:00:00 2001 From: Nikita Kudrin Date: Thu, 16 Jun 2022 14:44:58 +0300 Subject: [PATCH] [ide starter extended] run system metrics collection on each IDE launch GitOrigin-RevId: c21263f35c0f563cb8d6fedff55de79e9e1b4464 --- .../src/com/intellij/ide/starter/bus/Event.kt | 5 +- .../intellij/ide/starter/bus/EventState.kt | 11 ++ .../ide/starter/bus/EventTimelineState.kt | 9 -- .../ide/starter/bus/EventsReceiver.kt | 149 +++++++++--------- .../intellij/ide/starter/bus/Extensions.kt | 5 - .../com/intellij/ide/starter/bus/FlowBus.kt | 42 +++-- .../com/intellij/ide/starter/bus/Signal.kt | 3 +- .../bus/{EventBus.kt => StarterBus.kt} | 2 +- .../ide/starter/bus/StarterListener.kt | 3 + .../ide/starter/runner/IDERunContext.kt | 67 ++++---- .../ide/starter/runner/IdeLaunchEvent.kt | 6 + .../ide/starter/utils/testNameExtension.kt | 37 +++++ .../com/intellij/ide/starter/utils/utils.kt | 6 +- .../ide/starter/tests/unit/EventBusTest.kt | 16 +- .../tests/unit/PluginsInjectionTest.kt | 3 +- .../starter/tests/unit/RunIdeEventsTest.kt | 69 ++++++++ 16 files changed, 281 insertions(+), 152 deletions(-) create mode 100644 tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventState.kt delete mode 100644 tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventTimelineState.kt rename tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/{EventBus.kt => StarterBus.kt} (56%) create mode 100644 tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/StarterListener.kt create mode 100644 tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IdeLaunchEvent.kt create mode 100644 tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/testNameExtension.kt create mode 100644 tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/RunIdeEventsTest.kt diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Event.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Event.kt index a5304d97e591..b1c6013b084a 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Event.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Event.kt @@ -1,3 +1,6 @@ package com.intellij.ide.starter.bus -class Event \ No newline at end of file +open class Event( + state: EventState = EventState.UNDEFINED, + val data: T +) : Signal(state) \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventState.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventState.kt new file mode 100644 index 000000000000..6805d00fdba4 --- /dev/null +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventState.kt @@ -0,0 +1,11 @@ +package com.intellij.ide.starter.bus + +enum class EventState { + UNDEFINED, + + /** Right before the action */ + BEFORE, + + /** After the action was completed */ + AFTER +} \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventTimelineState.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventTimelineState.kt deleted file mode 100644 index e75dc2f64bad..000000000000 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventTimelineState.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.intellij.ide.starter.bus - -enum class EventTimelineState { - /** Right before the action */ - READY, - - /** After action was completed */ - FINISHED -} \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventsReceiver.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventsReceiver.kt index cc9c8db705f3..058448905e0f 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventsReceiver.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventsReceiver.kt @@ -1,5 +1,6 @@ package com.intellij.ide.starter.bus +import com.intellij.ide.starter.utils.catchAll import kotlinx.coroutines.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.drop @@ -10,89 +11,93 @@ import kotlinx.coroutines.flow.filterNotNull * @license Apache 2.0 https://github.com/Kosert/FlowBus/blob/master/LICENSE * Class for receiving events posted to [FlowBus] * - * @param bus [FlowBus] instance to subscribe to. If not set, [EventBus] will be used + * @param bus [FlowBus] instance to subscribe to. If not set, [StarterBus] will be used */ open class EventsReceiver @JvmOverloads constructor( - private val bus: FlowBus = EventBus + private val bus: FlowBus = StarterBus ) { - private val jobs = mutableMapOf, Job>() + private val jobs = mutableMapOf, Job>() - private var returnDispatcher: CoroutineDispatcher = Dispatchers.Default + private var returnDispatcher: CoroutineDispatcher = Dispatchers.Default - /** - * Set the `CoroutineDispatcher` which will be used to launch your callbacks. - * - * If this [EventsReceiver] was created on the main thread the default dispatcher will be [Dispatchers.Main]. - * In any other case [Dispatchers.Default] will be used. - */ - fun returnOn(dispatcher: CoroutineDispatcher): EventsReceiver { - returnDispatcher = dispatcher - return this + /** + * Set the `CoroutineDispatcher` which will be used to launch your callbacks. + * + * If this [EventsReceiver] was created on the main thread the default dispatcher will be [Dispatchers.Main]. + * In any other case [Dispatchers.Default] will be used. + */ + fun returnOn(dispatcher: CoroutineDispatcher): EventsReceiver { + returnDispatcher = dispatcher + return this + } + + /** + * Subscribe to events that are type of [clazz] with the given [callback] function. + * The [callback] can be called immediately if event of type [clazz] is present in the flow. + * + * @param clazz Type of event to subscribe to + * @param skipRetained Skips event already present in the flow. This is `false` by default + * @param callback The callback function + * @return This instance of [EventsReceiver] for chaining + */ + @JvmOverloads + fun subscribeTo( + clazz: Class, + skipRetained: Boolean = false, + callback: suspend (event: T) -> Unit + ): EventsReceiver { + + if (jobs.containsKey(clazz)) + throw IllegalArgumentException("Already subscribed for event type: $clazz") + + val exceptionHandler = CoroutineExceptionHandler { _, throwable -> + throw throwable } - /** - * Subscribe to events that are type of [clazz] with the given [callback] function. - * The [callback] can be called immediately if event of type [clazz] is present in the flow. - * - * @param clazz Type of event to subscribe to - * @param skipRetained Skips event already present in the flow. This is `false` by default - * @param callback The callback function - * @return This instance of [EventsReceiver] for chaining - */ - @JvmOverloads - fun subscribeTo( - clazz: Class, - skipRetained: Boolean = false, - callback: suspend (event: T) -> Unit - ): EventsReceiver { - - if (jobs.containsKey(clazz)) - throw IllegalArgumentException("Already subscribed for event type: $clazz") - - val exceptionHandler = CoroutineExceptionHandler { _, throwable -> - throw throwable + val job = CoroutineScope(Job() + Dispatchers.Default + exceptionHandler).launch { + bus.forEvent(clazz) + .drop(if (skipRetained) 1 else 0) + .filterNotNull() + .collect { + catchAll { + withContext(returnDispatcher) { callback(it) } + } } - - val job = CoroutineScope(Job() + Dispatchers.Default + exceptionHandler).launch { - bus.forEvent(clazz) - .drop(if (skipRetained) 1 else 0) - .filterNotNull() - .collect { withContext(returnDispatcher) { callback(it) } } - } - - jobs[clazz] = job - return this } - /** - * A variant of [subscribeTo] that uses an instance of [EventCallback] as callback. - * - * @param clazz Type of event to subscribe to - * @param skipRetained Skips event already present in the flow. This is `false` by default - * @param callback Interface with implemented callback function - * @return This instance of [EventsReceiver] for chaining - * @see [subscribeTo] - */ - @JvmOverloads - fun subscribeTo( - clazz: Class, - callback: EventCallback, - skipRetained: Boolean = false - ): EventsReceiver = subscribeTo(clazz, skipRetained) { callback.onEvent(it) } + jobs[clazz] = job + return this + } - /** - * Unsubscribe from events type of [clazz] - */ - fun unsubscribe(clazz: Class) { - jobs.remove(clazz)?.cancel() - } + /** + * A variant of [subscribeTo] that uses an instance of [EventCallback] as callback. + * + * @param clazz Type of event to subscribe to + * @param skipRetained Skips event already present in the flow. This is `false` by default + * @param callback Interface with implemented callback function + * @return This instance of [EventsReceiver] for chaining + * @see [subscribeTo] + */ + @JvmOverloads + fun subscribeTo( + clazz: Class, + callback: EventCallback, + skipRetained: Boolean = false + ): EventsReceiver = subscribeTo(clazz, skipRetained) { callback.onEvent(it) } - /** - * Unsubscribe from all events - */ - fun unsubscribe() { - jobs.values.forEach { it.cancel() } - jobs.clear() - } + /** + * Unsubscribe from events type of [clazz] + */ + fun unsubscribe(clazz: Class) { + jobs.remove(clazz)?.cancel() + } + + /** + * Unsubscribe from all events + */ + fun unsubscribe() { + jobs.values.forEach { it.cancel() } + jobs.clear() + } } diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Extensions.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Extensions.kt index 7761c2eee7ce..754491fd754e 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Extensions.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Extensions.kt @@ -10,11 +10,6 @@ package com.intellij.ide.starter.bus */ inline fun FlowBus.dropEvent() = dropEvent(T::class.java) -/** - * @see FlowBus.getLastEvent - */ -inline fun FlowBus.getLastEvent(): T? = getLastEvent(T::class.java) - /** * @see FlowBus.getFlow */ diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/FlowBus.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/FlowBus.kt index 41d43d03c1f7..adf6740b904a 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/FlowBus.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/FlowBus.kt @@ -1,25 +1,30 @@ package com.intellij.ide.starter.bus -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch /** * @author https://github.com/Kosert/FlowBus * @license Apache 2.0 https://github.com/Kosert/FlowBus/blob/master/LICENSE * - * This class holds all state flows and handles event posting. - * You can use [GlobalBus] that is just plain instance of this class or create your own implementation. + * This class holds all shared flows and handles event posting. + * You can use [StarterBus] that is just plain instance of this class or create your own implementation. */ open class FlowBus { - private val flows = mutableMapOf, MutableStateFlow<*>>() + private val flows = mutableMapOf, MutableSharedFlow<*>>() /** - * Gets a MutableStateFlow for events of the given type. Creates new if one doesn't exist. - * @return MutableStateFlow for events that are instances of clazz + * Gets a MutableSharedFlow for events of the given type. Creates new if one doesn't exist. + * @return MutableSharedFlow for events that are instances of clazz */ - internal fun forEvent(clazz: Class): MutableStateFlow { - return flows.getOrPut(clazz) { MutableStateFlow(null) } as MutableStateFlow + internal fun forEvent(clazz: Class): MutableSharedFlow { + return flows.getOrPut(clazz) { + MutableSharedFlow(extraBufferCapacity = 5000) + } as MutableSharedFlow } /** @@ -33,43 +38,36 @@ open class FlowBus { * @see [SharedFlow] */ fun getFlow(clazz: Class): Flow { - return forEvent(clazz).asStateFlow().filterNotNull() + return forEvent(clazz).filterNotNull() } /** - * Posts new event to StateFlow of the [event] type. + * Posts new event to SharedFlow of the [event] type. * @param retain If the [event] should be retained in the flow for future subscribers. This is true by default. */ @JvmOverloads fun post(event: T, retain: Boolean = true) { val flow = forEvent(event.javaClass) + flow.tryEmit(event).also { if (!it) - throw IllegalStateException("StateFlow cannot take element, this should never happen") + throw IllegalStateException("SharedFlow cannot take element, this should never happen") } if (!retain) { // without starting a coroutine here, the event is dropped immediately // and not delivered to subscribers - CoroutineScope(Job() + Dispatchers.Unconfined).launch { + CoroutineScope(Job() + Dispatchers.Default).launch { dropEvent(event.javaClass) } } } - /** - * Returns last posted event that was instance of [clazz] or `null` if no event of the given type is retained. - * @return Retained event that is instance of [clazz] - */ - fun getLastEvent(clazz: Class): T? { - return flows.getOrElse(clazz) { null }?.value as T? - } - /** * Removes retained event of type [clazz] */ fun dropEvent(clazz: Class) { if (!flows.contains(clazz)) return - val channel = flows[clazz] as MutableStateFlow + val channel = flows[clazz] as MutableSharedFlow channel.tryEmit(null) } @@ -78,7 +76,7 @@ open class FlowBus { */ fun dropAll() { flows.values.forEach { - (it as MutableStateFlow).tryEmit(null) + (it as MutableSharedFlow).tryEmit(null) } } } \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Signal.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Signal.kt index ec52a285e0e5..267ee3463fb2 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Signal.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/Signal.kt @@ -1,4 +1,3 @@ package com.intellij.ide.starter.bus -/** Event, that works as a marker, that some action happened */ -class Signal(val state: EventTimelineState) \ No newline at end of file +open class Signal(val state: EventState = EventState.UNDEFINED) \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventBus.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/StarterBus.kt similarity index 56% rename from tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventBus.kt rename to tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/StarterBus.kt index ff8bc80fd849..83a271f24170 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/EventBus.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/StarterBus.kt @@ -1,3 +1,3 @@ package com.intellij.ide.starter.bus -object EventBus : FlowBus() \ No newline at end of file +object StarterBus : FlowBus() \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/StarterListener.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/StarterListener.kt new file mode 100644 index 000000000000..c2b38fbc704c --- /dev/null +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/bus/StarterListener.kt @@ -0,0 +1,3 @@ +package com.intellij.ide.starter.bus + +object StarterListener : EventsReceiver() \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IDERunContext.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IDERunContext.kt index 2f47d8a119ab..fd2d5a74c29f 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IDERunContext.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IDERunContext.kt @@ -1,5 +1,7 @@ package com.intellij.ide.starter.runner +import com.intellij.ide.starter.bus.EventState +import com.intellij.ide.starter.bus.StarterBus import com.intellij.ide.starter.di.di import com.intellij.ide.starter.exec.ExecOutputRedirect import com.intellij.ide.starter.exec.ExecTimeoutException @@ -90,6 +92,8 @@ data class IDERunContext( // TODO: refactor this private fun prepareToRunIDE(): IDEStartResult { + StarterBus.post(IdeLaunchEvent(EventState.BEFORE, this)) + deleteSavedAppStateOnMac() val paths = testContext.paths val logsDir = paths.logsDir.createDirectories() @@ -266,37 +270,42 @@ data class IDERunContext( } finally { - if (SystemInfo.isWindows) { - destroyGradleDaemonProcessIfExists() - } - - listOf(heapDumpOnOomDirectory, jvmCrashLogDirectory).filter { dir -> - dir.listDirectoryEntries().isEmpty() - }.forEach { it.toFile().deleteRecursively() } - - ErrorReporter.reportErrorsAsFailedTests(logsDir / "script-errors", contextName) - val (artifactPath, artifactName) = if (successfulRun) contextName to "logs" else "run/$contextName" to "crash" - testContext.publishArtifact(logsDir, artifactPath, formatArtifactName(artifactName, testContext.testName)) - val snapshotFiles = Files.list(testContext.paths.snapshotsDir).use { it.filter { it.isRegularFile() }.toList() } - if (snapshotFiles.isNotEmpty()) { - testContext.publishArtifact(testContext.paths.snapshotsDir, contextName, formatArtifactName("snapshots", testContext.testName)) - } - if (codeBuilder != null) { - host.tearDown(testContext) - } - - val closeContext = object : IDERunCloseContext { - override val wasRunSuccessful: Boolean = successfulRun - } - - closeHandlers.forEach { - try { - it.invoke(closeContext) + try { + if (SystemInfo.isWindows) { + destroyGradleDaemonProcessIfExists() } - catch (t: Throwable) { - logOutput("Failed to complete close step. ${t.message}.\n" + t) - t.printStackTrace(System.err) + + listOf(heapDumpOnOomDirectory, jvmCrashLogDirectory).filter { dir -> + dir.listDirectoryEntries().isEmpty() + }.forEach { it.toFile().deleteRecursively() } + + ErrorReporter.reportErrorsAsFailedTests(logsDir / "script-errors", contextName) + val (artifactPath, artifactName) = if (successfulRun) contextName to "logs" else "run/$contextName" to "crash" + testContext.publishArtifact(logsDir, artifactPath, formatArtifactName(artifactName, testContext.testName)) + val snapshotFiles = Files.list(testContext.paths.snapshotsDir).use { it.filter { it.isRegularFile() }.toList() } + if (snapshotFiles.isNotEmpty()) { + testContext.publishArtifact(testContext.paths.snapshotsDir, contextName, formatArtifactName("snapshots", testContext.testName)) } + if (codeBuilder != null) { + host.tearDown(testContext) + } + + val closeContext = object : IDERunCloseContext { + override val wasRunSuccessful: Boolean = successfulRun + } + + closeHandlers.forEach { + try { + it.invoke(closeContext) + } + catch (t: Throwable) { + logOutput("Failed to complete close step. ${t.message}.\n" + t) + t.printStackTrace(System.err) + } + } + } + finally { + StarterBus.post(IdeLaunchEvent(EventState.AFTER, this)) } } } diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IdeLaunchEvent.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IdeLaunchEvent.kt new file mode 100644 index 000000000000..2738d16d036b --- /dev/null +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/runner/IdeLaunchEvent.kt @@ -0,0 +1,6 @@ +package com.intellij.ide.starter.runner + +import com.intellij.ide.starter.bus.Event +import com.intellij.ide.starter.bus.EventState + +class IdeLaunchEvent(state: EventState, runContext: IDERunContext) : Event(state, runContext) \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/testNameExtension.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/testNameExtension.kt new file mode 100644 index 000000000000..e8703ddbcfc1 --- /dev/null +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/testNameExtension.kt @@ -0,0 +1,37 @@ +package com.intellij.ide.starter.utils + +import java.util.* +import kotlin.io.path.Path +import kotlin.io.path.name + + +/** + * Format: testMethodName => test-method-name + */ +fun String.hyphenateTestName(): String { + + fun hyphenateString(input: String) = input + .replace(" ", "-").trim() + .replaceFirstChar { it.lowercase(Locale.getDefault()) }.toCharArray() + .map { + if (it.isUpperCase()) "-${it.lowercaseChar()}" + else it + } + .joinToString(separator = "") + + val hyphenatedPath = try { + val originalPath = Path(this) + + var convertedPath = Path("") + (0 until originalPath.nameCount).map { pathNameIndex -> + convertedPath = convertedPath.resolve(hyphenateString(originalPath.getName(pathNameIndex).name)) + } + + convertedPath.toString() + } + catch (_: Exception) { + return hyphenateString(this) + } + + return hyphenatedPath +} \ No newline at end of file diff --git a/tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/utils.kt b/tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/utils.kt index 67e849b7245e..c62582e4b8de 100644 --- a/tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/utils.kt +++ b/tools/intellij.ide.starter/src/com/intellij/ide/starter/utils/utils.kt @@ -18,7 +18,7 @@ import java.time.format.DateTimeFormatter import kotlin.io.path.* import kotlin.time.Duration -fun formatArtifactName(artifactType: String, testName: String): String{ +fun formatArtifactName(artifactType: String, testName: String): String { val testNameFormatted = testName.replace("/", "-").replace(" ", "") val time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) return "$artifactType-$testNameFormatted-$time" @@ -35,7 +35,7 @@ inline fun catchAll(action: () -> Unit) { action() } catch (t: Throwable) { - logOutput("CatchAll swallowed error: ${t.message}") + logError("CatchAll swallowed error: ${t.message}") logError(getThrowableText(t)) } } @@ -135,7 +135,7 @@ fun String.withIndent(indent: String = " "): String = lineSequence().map { "$in private fun quoteArg(arg: String): String { val specials = " #'\"\n\r\t\u000c" - if(!specials.any { arg.contains(it) }){ + if (!specials.any { arg.contains(it) }) { return arg } diff --git a/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/EventBusTest.kt b/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/EventBusTest.kt index 213575a028a6..0e079c18c3c1 100644 --- a/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/EventBusTest.kt +++ b/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/EventBusTest.kt @@ -1,6 +1,9 @@ package com.intellij.ide.starter.tests.unit -import com.intellij.ide.starter.bus.* +import com.intellij.ide.starter.bus.Signal +import com.intellij.ide.starter.bus.StarterBus +import com.intellij.ide.starter.bus.StarterListener +import com.intellij.ide.starter.bus.subscribe import io.kotest.assertions.withClue import io.kotest.matchers.shouldBe import kotlinx.coroutines.delay @@ -13,7 +16,6 @@ import java.util.concurrent.atomic.AtomicBoolean import kotlin.time.Duration class EventBusTest { - private val receiver = EventsReceiver() private var isEventHappened: AtomicBoolean = AtomicBoolean(false) private fun checkIsEventFired(shouldEventBeFired: Boolean, isEventFiredGetter: () -> Boolean) { @@ -22,7 +24,7 @@ class EventBusTest { withClue("Event should $shouldNotMessage be fired") { runBlocking { try { - withTimeout(timeout = Duration.seconds(5)) { + withTimeout(timeout = Duration.seconds(10)) { while (shouldEventBeFired != isEventFiredGetter()) { delay(Duration.milliseconds(500)) } @@ -43,19 +45,19 @@ class EventBusTest { @After fun afterEach() { - receiver.unsubscribe() + StarterListener.unsubscribe() } @Test fun filteringEventsByTypeIsWorking() { - receiver.subscribe { event: Int -> + StarterListener.subscribe { event: Signal -> isEventHappened.set(true) } - EventBus.post(Signal(EventTimelineState.READY)) + StarterBus.post(2) checkIsEventFired(false) { isEventHappened.get() } - EventBus.post(2) + StarterBus.post(Signal()) checkIsEventFired(true) { isEventHappened.get() } } } \ No newline at end of file diff --git a/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/PluginsInjectionTest.kt b/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/PluginsInjectionTest.kt index 0496dbf81a73..3c439d558f0c 100644 --- a/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/PluginsInjectionTest.kt +++ b/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/PluginsInjectionTest.kt @@ -5,6 +5,7 @@ import com.intellij.ide.starter.ide.IDETestContext import com.intellij.ide.starter.ide.InstalledIDE import com.intellij.ide.starter.models.TestCase import com.intellij.ide.starter.path.IDEDataPaths +import com.intellij.ide.starter.utils.hyphenateTestName import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -30,7 +31,7 @@ class PluginsInjectionTest { @Test fun theSameIDETestContextShouldBeReferencedInPluginConfigurator() { - val testName = "example test" + val testName = object {}.javaClass.enclosingMethod.name.hyphenateTestName() val paths = IDEDataPaths.createPaths(testName, testDirectory, useInMemoryFs = false) val projectHome = testCase.projectInfo?.resolveProjectHome() diff --git a/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/RunIdeEventsTest.kt b/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/RunIdeEventsTest.kt new file mode 100644 index 000000000000..1977fe1e4d8a --- /dev/null +++ b/tools/intellij.ide.starter/testSrc/com/intellij/ide/starter/tests/unit/RunIdeEventsTest.kt @@ -0,0 +1,69 @@ +package com.intellij.ide.starter.tests.unit + +import com.intellij.ide.starter.bus.EventState +import com.intellij.ide.starter.bus.StarterListener +import com.intellij.ide.starter.bus.subscribe +import com.intellij.ide.starter.di.di +import com.intellij.ide.starter.ide.IDETestContext +import com.intellij.ide.starter.ide.InstalledIDE +import com.intellij.ide.starter.ide.command.CommandChain +import com.intellij.ide.starter.models.TestCase +import com.intellij.ide.starter.path.IDEDataPaths +import com.intellij.ide.starter.runner.IdeLaunchEvent +import com.intellij.ide.starter.utils.catchAll +import com.intellij.ide.starter.utils.hyphenateTestName +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.io.TempDir +import org.kodein.di.direct +import org.kodein.di.instance +import org.mockito.Mock +import org.mockito.junit.jupiter.MockitoExtension +import java.nio.file.Path + + +@ExtendWith(MockitoExtension::class) +class RunIdeEventsTest { + + @TempDir + lateinit var testDirectory: Path + + @Mock + private lateinit var testCase: TestCase + + @Mock + private lateinit var ide: InstalledIDE + + @Test + fun eventsForIdeLaunchShouldBeFired() { + val testName = object {}.javaClass.enclosingMethod.name.hyphenateTestName() + val paths = IDEDataPaths.createPaths(testName, testDirectory, useInMemoryFs = false) + + val projectHome = testCase.projectInfo?.resolveProjectHome() + val context = IDETestContext(paths = paths, + ide = ide, + testCase = testCase, + testName = testName, + _resolvedProjectHome = projectHome, + patchVMOptions = { this }, + ciServer = di.direct.instance()) + + val firedEvents = mutableListOf() + + StarterListener.subscribe { event: IdeLaunchEvent -> firedEvents.add(event) } + + catchAll { + context.runIDE(commands = CommandChain()) + } + + assertSoftly { + withClue("During IDE run should be fired 2 events: before ide start and after ide finished") { + firedEvents.first().state.shouldBe(EventState.BEFORE) + firedEvents.last().state.shouldBe(EventState.AFTER) + } + } + } +} \ No newline at end of file