[AutoSync] cleanup: migrated auto-sync test MockProjectAware on the platform operation wait system

GitOrigin-RevId: ba019de3fd61bf5965716ad730580a6d0cc301dc
This commit is contained in:
Sergei Vorobyov
2024-07-23 19:47:35 +02:00
committed by intellij-monorepo-bot
parent 5937b20901
commit b4aeb3fa31
6 changed files with 146 additions and 67 deletions

View File

@@ -32,5 +32,6 @@
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.workspace.jps" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.backend.workspace" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.backend.observation" scope="TEST" />
</component>
</module>

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.externalSystem.autoimport
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemModificationType.*
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTrackerSettings.AutoReloadType.*
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemRefreshStatus.FAILURE
@@ -10,6 +11,8 @@ import com.intellij.openapi.externalSystem.autoimport.ExternalSystemSettingsFile
import com.intellij.openapi.externalSystem.autoimport.MockProjectAware.ReloadCollisionPassType
import com.intellij.openapi.externalSystem.model.ProjectSystemId
import com.intellij.openapi.externalSystem.util.Parallel.Companion.parallel
import com.intellij.openapi.externalSystem.util.runReadAction
import com.intellij.openapi.externalSystem.util.runWriteActionAndWait
import com.intellij.openapi.util.Ref
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.refreshVfs
@@ -473,7 +476,9 @@ class AutoReloadTest : AutoReloadTestCase() {
fun `test document changes between save`() {
test { settingsFile ->
val settingsDocument = settingsFile.getDocument()
val settingsDocument = runReadAction {
settingsFile.getDocument()
}
settingsDocument.replaceContent("println 'hello'")
assertStateAndReset(numReload = 0, notified = true, event = "change")
@@ -643,11 +648,11 @@ class AutoReloadTest : AutoReloadTestCase() {
assertActivationStatus(projectId1, projectId2, event = "refresh project")
}
fun `test merging of refreshes with different nature`() {
fun `test merging of refreshes with different nature (parallel)`() {
test { settingsFile ->
enableAsyncExecution()
waitForProjectReloadFinish {
waitForAllProjectReloads {
parallel {
thread {
settingsFile.modify(EXTERNAL)
@@ -930,10 +935,16 @@ class AutoReloadTest : AutoReloadTestCase() {
fun `test settings file modification by document before sync`() {
test { settingsFile ->
val settingsDocument = settingsFile.getDocument()
val settingsDocument = runReadAction {
settingsFile.getDocument()
}
settingsDocument.appendString(SAMPLE_TEXT + "\n")
onceWhenReloading { settingsDocument.saveToDisk() }
onceWhenReloading {
runWriteActionAndWait {
settingsDocument.saveToDisk()
}
}
forceReloadProject()
assertStateAndReset(numReload = 1, notified = false, event = "settings file modified by document before sync")
}
@@ -944,9 +955,7 @@ class AutoReloadTest : AutoReloadTestCase() {
whenReloading(1) {
settingsFile.modify(EXTERNAL)
}
waitForProjectReloadFinish(2) {
settingsFile.modify(EXTERNAL)
}
settingsFile.modify(EXTERNAL)
assertStateAndReset(numReload = 2, notified = false, event = "settings file modified during sync")
}
}
@@ -956,9 +965,7 @@ class AutoReloadTest : AutoReloadTestCase() {
whenReloadStarted(1) {
forceReloadProject()
}
waitForProjectReloadFinish(2) {
forceReloadProject()
}
forceReloadProject()
assertStateAndReset(numReload = 2, notified = false, event = "test force sync action during sync")
}
}
@@ -976,7 +983,7 @@ class AutoReloadTest : AutoReloadTestCase() {
whenReloadStarted(expectedRefreshes - 1) {
forceReloadProject()
}
waitForProjectReloadFinish(expectedRefreshes) {
waitForAllProjectReloads {
forceReloadProject()
}
assertStateAndReset(numReload = expectedRefreshes, notified = false, event = "reloads")
@@ -998,7 +1005,7 @@ class AutoReloadTest : AutoReloadTestCase() {
whenReloading(expectedRefreshes - 1) {
settingsFile.modify(EXTERNAL)
}
waitForProjectReloadFinish(expectedRefreshes) {
waitForAllProjectReloads {
settingsFile.modify(EXTERNAL)
}
assertStateAndReset(numReload = expectedRefreshes, notified = false, event = "reloads")
@@ -1026,24 +1033,28 @@ class AutoReloadTest : AutoReloadTestCase() {
}
}
fun `test merge project reloads`() {
fun `test merge project reloads (parallel)`() {
test { settingsFile ->
enableAsyncExecution()
setDispatcherMergingSpan(100)
val alarm = Alarm()
// see AutoImportProjectTracker.scheduleDelayedSmartProjectReload
setDispatcherMergingSpan(100) // total merging delay 1000ms
val alarm = Alarm(Alarm.ThreadToUse.POOLED_THREAD, testDisposable)
repeat(10) {
val promise = AsyncPromise<Unit>()
alarm.addRequest({
waitForProjectReloadFinish {
markDirty()
scheduleProjectReload()
}
promise.setResult(Unit)
}, 500)
waitForProjectReloadFinish {
alarm.addRequest(Runnable {
waitForAllProjectReloads {
markDirty()
scheduleProjectReload()
}
promise.setResult(Unit)
}, 500)
waitForAllProjectReloads {
settingsFile.modify(EXTERNAL)
}
PlatformTestUtil.waitForPromise(promise, TimeUnit.SECONDS.toMillis(10))
invokeAndWaitIfNeeded {
PlatformTestUtil.waitForPromise(promise, TimeUnit.SECONDS.toMillis(10))
}
assertStateAndReset(numReload = 1, notified = false, event = "project reload")
}
}

View File

@@ -3,7 +3,6 @@ package com.intellij.openapi.externalSystem.autoimport
import com.intellij.ide.file.BatchFileChangeListener
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.components.ComponentManager
import com.intellij.openapi.editor.Document
@@ -15,19 +14,19 @@ import com.intellij.openapi.externalSystem.autoimport.ExternalSystemProjectTrack
import com.intellij.openapi.externalSystem.autoimport.MockProjectAware.ReloadCollisionPassType
import com.intellij.openapi.externalSystem.importing.ProjectResolverPolicy
import com.intellij.openapi.externalSystem.service.project.autoimport.ProjectAware
import com.intellij.openapi.util.io.*
import com.intellij.openapi.progress.util.BackgroundTaskUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Computable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.io.getResolvedPath
import com.intellij.openapi.util.io.toCanonicalPath
import com.intellij.openapi.util.use
import com.intellij.openapi.vfs.*
import com.intellij.platform.externalSystem.testFramework.ExternalSystemTestCase
import com.intellij.platform.externalSystem.testFramework.ExternalSystemTestUtil.TEST_EXTERNAL_SYSTEM_ID
import com.intellij.platform.externalSystem.testFramework.TestExternalSystemManager
import com.intellij.testFramework.ExtensionTestUtil
import com.intellij.testFramework.PlatformTestUtil
import com.intellij.testFramework.refreshVfs
import com.intellij.testFramework.replaceService
import com.intellij.testFramework.utils.editor.saveToDisk
@@ -37,16 +36,16 @@ import com.intellij.testFramework.utils.vfs.createFile
import com.intellij.testFramework.utils.vfs.deleteRecursively
import com.intellij.testFramework.utils.vfs.getFile
import com.intellij.testFramework.utils.vfs.refreshAndGetVirtualFile
import org.jetbrains.concurrency.AsyncPromise
import java.nio.file.Path
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import kotlin.io.path.appendText
import kotlin.io.path.readText
import kotlin.io.path.writeText
@Suppress("unused", "MemberVisibilityCanBePrivate", "SameParameterValue")
abstract class AutoReloadTestCase : ExternalSystemTestCase() {
override fun runInDispatchThread() = false
override fun getTestsTempDir() = "tmp${System.currentTimeMillis()}"
override fun getExternalSystemConfigFileName() = throw UnsupportedOperationException()
@@ -238,12 +237,14 @@ abstract class AutoReloadTestCase : ExternalSystemTestCase() {
projectTrackerSettings.loadState(state.second)
}
protected fun assertProjectAware(projectAware: MockProjectAware,
numReload: Int? = null,
numSettingsAccess: Int? = null,
numSubscribing: Int? = null,
numUnsubscribing: Int? = null,
event: String) {
protected fun assertProjectAware(
projectAware: MockProjectAware,
numReload: Int? = null,
numSettingsAccess: Int? = null,
numSubscribing: Int? = null,
numUnsubscribing: Int? = null,
event: String,
) {
if (numReload != null) assertCountEvent(numReload, projectAware.reloadCounter.get(), "project reload", event)
if (numSettingsAccess != null) assertCountEvent(numSettingsAccess, projectAware.settingsAccessCounter.get(), "access to settings",
event)
@@ -320,7 +321,7 @@ abstract class AutoReloadTestCase : ExternalSystemTestCase() {
protected fun testWithDummyExternalSystem(
autoImportAwareCondition: Ref<Boolean>? = null,
test: DummyExternalSystemTestBench.(VirtualFile) -> Unit
test: DummyExternalSystemTestBench.(VirtualFile) -> Unit,
) {
val externalSystemManagers = ExternalSystemManager.EP_NAME.extensionList + TestExternalSystemManager(myProject)
ExtensionTestUtil.maskExtensions(ExternalSystemManager.EP_NAME, externalSystemManagers, testRootDisposable)
@@ -354,7 +355,7 @@ abstract class AutoReloadTestCase : ExternalSystemTestCase() {
fun withProjectTracker(
state: Pair<AutoImportProjectTracker.State, AutoImportProjectTrackerSettings.State> =
AutoImportProjectTracker.State() to AutoImportProjectTrackerSettings.State(),
test: (Disposable) -> Unit
test: (Disposable) -> Unit,
): Pair<AutoImportProjectTracker.State, AutoImportProjectTrackerSettings.State> {
return myProject.replaceService(ExternalSystemProjectTrackerSettings::class.java, AutoImportProjectTrackerSettings()) {
myProject.replaceService(ExternalSystemProjectTracker::class.java, AutoImportProjectTracker(myProject)) {
@@ -372,7 +373,7 @@ abstract class AutoReloadTestCase : ExternalSystemTestCase() {
projectAware: MockProjectAware,
state: Pair<AutoImportProjectTracker.State, AutoImportProjectTrackerSettings.State> =
AutoImportProjectTracker.State() to AutoImportProjectTrackerSettings.State(),
test: SimpleTestBench.() -> Unit
test: SimpleTestBench.() -> Unit,
): Pair<AutoImportProjectTracker.State, AutoImportProjectTrackerSettings.State> {
return withProjectTracker(state) {
register(projectAware, parentDisposable = it)
@@ -409,7 +410,7 @@ abstract class AutoReloadTestCase : ExternalSystemTestCase() {
}
protected fun mockProjectAware(projectId: ExternalSystemProjectId = ExternalSystemProjectId(TEST_EXTERNAL_SYSTEM_ID, projectPath)) =
MockProjectAware(projectId, testDisposable)
MockProjectAware(projectId, myProject, testDisposable)
protected inner class SimpleTestBench(val projectAware: MockProjectAware) {
@@ -499,21 +500,8 @@ abstract class AutoReloadTestCase : ExternalSystemTestCase() {
projectAware.resetAssertionCounters()
}
fun waitForProjectReloadFinish(numExpectedReloads: Int = 1, action: () -> Unit) {
require(numExpectedReloads > 0)
Disposer.newDisposable(testDisposable, "waitForProjectReloadFinish").use { parentDisposable ->
val promise = AsyncPromise<ExternalSystemRefreshStatus>()
val uncompletedReloads = AtomicInteger(numExpectedReloads)
whenReloadFinished(parentDisposable) { status ->
if (uncompletedReloads.decrementAndGet() == 0) {
promise.setResult(status)
}
}
action()
invokeAndWaitIfNeeded {
PlatformTestUtil.waitForPromise(promise, TimeUnit.SECONDS.toMillis(10))
}
}
fun waitForAllProjectReloads(action: () -> Unit) {
projectAware.waitForAllProjectReloads(action)
}
}
@@ -534,13 +522,15 @@ abstract class AutoReloadTestCase : ExternalSystemTestCase() {
resetAssertionCounters()
}
fun assertState(numReload: Int? = null,
numReloadStarted: Int? = null,
numReloadFinished: Int? = null,
numSubscribing: Int? = null,
numUnsubscribing: Int? = null,
autoReloadType: AutoReloadType = SELECTIVE,
event: String) {
fun assertState(
numReload: Int? = null,
numReloadStarted: Int? = null,
numReloadFinished: Int? = null,
numSubscribing: Int? = null,
numUnsubscribing: Int? = null,
autoReloadType: AutoReloadType = SELECTIVE,
event: String,
) {
if (numReload != null) assertCountEvent(numReload, projectAware.reloadCounter.get(), "project reload", event)
if (numReloadStarted != null) assertCountEvent(numReloadStarted, projectAware.startReloadCounter.get(), "project before reload", event)
if (numReloadFinished != null) assertCountEvent(numReloadFinished, projectAware.finishReloadCounter.get(), "project after reload", event)

View File

@@ -2,6 +2,7 @@
package com.intellij.openapi.externalSystem.autoimport
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemRefreshStatus.SUCCESS
import com.intellij.openapi.externalSystem.autoimport.MockProjectAware.ReloadCollisionPassType.*
@@ -11,17 +12,24 @@ import com.intellij.openapi.observable.operation.core.AtomicOperationTrace
import com.intellij.openapi.observable.operation.core.isOperationInProgress
import com.intellij.openapi.observable.operation.core.traceRun
import com.intellij.openapi.observable.operation.core.withCompletedOperation
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toCanonicalPath
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.backend.observation.ActivityKey
import com.intellij.platform.backend.observation.Observation
import com.intellij.platform.backend.observation.trackActivityBlocking
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
import kotlin.concurrent.thread
import kotlin.time.Duration.Companion.seconds
class MockProjectAware(
override val projectId: ExternalSystemProjectId,
private val parentDisposable: Disposable
private val project: Project,
private val parentDisposable: Disposable,
) : ExternalSystemProjectAware {
val subscribeCounter = AtomicInteger(0)
@@ -145,12 +153,29 @@ class MockProjectAware(
private fun background(action: () -> Unit) {
if (AutoImportProjectTracker.isAsyncChangesProcessing) {
thread(block = action)
ApplicationManager.getApplication().executeOnPooledThread(action)
}
else {
action()
}
}
fun <R> waitForAllProjectReloads(action: () -> R): R {
return project.trackActivityBlocking(MockProjectReloadActivityKey, action)
.also {
runBlocking {
withTimeout(10.seconds) {
Observation.awaitConfiguration(project) { message ->
println(message)
}
}
}
}
}
private object MockProjectReloadActivityKey : ActivityKey {
override val presentableName: String = "mock project reload"
}
enum class ReloadCollisionPassType { DUPLICATE, CANCEL, IGNORE }
}

View File

@@ -0,0 +1,49 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.externalSystem.autoimport
import com.intellij.openapi.externalSystem.autoimport.ExternalSystemModificationType.EXTERNAL
import com.intellij.openapi.externalSystem.util.Parallel.Companion.parallel
// Test for the mock auto-sync test infrastructure
class MockProjectAwareTest : AutoReloadTestCase() {
fun `test wait for single mock reload function (parallel)`() {
test {
enableAsyncExecution()
waitForAllProjectReloads {
forceReloadProject()
}
assertStateAndReset(numReload = 1, notified = false, event = "project reload")
}
}
fun `test wait for mock reload function (parallel)`() {
test {
enableAsyncExecution()
val expectedReloads = 10
parallel {
repeat(expectedReloads) {
thread {
waitForAllProjectReloads {
forceReloadProject()
}
}
}
}
assertStateAndReset(numReload = expectedReloads, notified = false, event = "$expectedReloads parallel project reloads")
}
}
fun `test wait for indirect mock reload function (parallel)`() {
test { settingsFile ->
enableAsyncExecution()
waitForAllProjectReloads {
settingsFile.modify(EXTERNAL)
}
assertStateAndReset(numReload = 1, notified = false, event = "project reload")
}
}
}

View File

@@ -1,6 +1,7 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.openapi.externalSystem.util
import com.intellij.openapi.application.invokeAndWaitIfNeeded
import com.intellij.testFramework.PlatformTestUtil
import org.jetbrains.concurrency.AsyncPromise
import java.util.concurrent.CountDownLatch
@@ -32,7 +33,9 @@ class Parallel private constructor() {
pool.configure()
pool.start.countDown()
for (promise in pool.promises) {
PlatformTestUtil.waitForPromise(promise)
invokeAndWaitIfNeeded {
PlatformTestUtil.waitForPromise(promise)
}
}
}
}