IJPL-191229 prepare to fix parallel blocking access to PathMacros (part 2 - rename old method to initComponentBlocking)

GitOrigin-RevId: 5bf201800edf138b10571ce12d40caa08b6039cd
This commit is contained in:
Vladimir Krivosheev
2025-06-10 08:07:12 +02:00
committed by intellij-monorepo-bot
parent add197a048
commit ba405b64db
21 changed files with 170 additions and 113 deletions

View File

@@ -127,7 +127,11 @@ abstract class ComponentStoreImpl : IComponentStore {
storageManager.clearStorages()
}
override fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
override suspend fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
initComponentBlocking(component = component, serviceDescriptor = serviceDescriptor, pluginId = pluginId)
}
override fun initComponentBlocking(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
var componentName: String? = null
try {
@Suppress("DEPRECATION")
@@ -180,10 +184,14 @@ abstract class ComponentStoreImpl : IComponentStore {
if (e is ControlFlowException) {
throw e
}
LOG.error(PluginException("Cannot init component state (componentName=$componentName, componentClass=${component.javaClass.simpleName})", e, pluginId))
error(PluginException("Cannot init component state (componentName=$componentName, componentClass=${component.javaClass.simpleName})", e, pluginId))
}
}
protected open fun error(error: PluginException) {
LOG.error(error)
}
private fun initJdom(@Suppress("DEPRECATION") component: com.intellij.openapi.util.JDOMExternalizable, pluginId: PluginId): String {
if (component.javaClass.name !in ignoredDeprecatedJDomExternalizableComponents) {
LOG.error(PluginException("""

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.configurationStore
import com.intellij.openapi.application.EDT
@@ -36,7 +36,14 @@ abstract class ComponentStoreWithExtraComponents : ComponentStoreImpl() {
result
}
final override fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
final override fun initComponentBlocking(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
if (component is SettingsSavingComponent) {
asyncSettingsSavingComponents.drop()
}
super.initComponentBlocking(component = component, serviceDescriptor = serviceDescriptor, pluginId = pluginId)
}
final override suspend fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
if (component is SettingsSavingComponent) {
asyncSettingsSavingComponents.drop()
}

View File

@@ -12,7 +12,10 @@ object NonPersistentModuleStore : IComponentStore {
override val storageManager: StateStorageManager
get() = NonPersistentStateStorageManager
override fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
override fun initComponentBlocking(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
}
override suspend fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
}
override fun unloadComponent(component: Any) {}

View File

@@ -2,6 +2,7 @@
package com.intellij.configurationStore
import com.intellij.configurationStore.schemeManager.ROOT_CONFIG
import com.intellij.diagnostic.PluginException
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.*
@@ -19,7 +20,6 @@ import com.intellij.util.xmlb.XmlSerializerUtil
import com.intellij.util.xmlb.annotations.Attribute
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.assertj.core.data.MapEntry
import org.intellij.lang.annotations.Language
import org.junit.Assert.*
@@ -69,13 +69,13 @@ class ApplicationStoreTest {
}
@Test
fun `load from stream provider`() {
fun `load from stream provider`(): Unit = runBlocking(Dispatchers.Default) {
val component = SeveralStoragesConfigured()
val streamProvider = MyStreamProvider()
val map = HashMap<String, String>()
val fileSpec = "new.xml"
map[fileSpec] = "<application>\n <component name=\"A\" foo=\"newValue\" />\n</application>"
map.put(fileSpec, "<application>\n <component name=\"A\" foo=\"newValue\" />\n</application>")
streamProvider.data.put(RoamingType.DEFAULT, map)
val storageManager = componentStore.storageManager
@@ -141,7 +141,7 @@ class ApplicationStoreTest {
}
@Test
fun `import settings`() {
fun `import settings`() = runBlocking<Unit>(Dispatchers.Default) {
val component = A()
componentStore.initComponent(component, null, PluginManagerCore.CORE_ID)
@@ -232,7 +232,7 @@ class ApplicationStoreTest {
private fun createComponentData(fooValue: String, componentName: String = "A") = """<component name="$componentName" foo="$fooValue" />"""
@Test
fun `remove data from deprecated storage if another component data exists`() = runBlocking<Unit> {
fun `remove data from deprecated storage if another component data exists`() = runBlocking<Unit>(Dispatchers.Default) {
val data = createComponentData("new")
val oldFile = writeConfig("old.xml", """<application>
<component name="OtherComponent" foo="old" />
@@ -254,7 +254,7 @@ class ApplicationStoreTest {
}
@Test
fun `don't save if only format is changed`() = runBlocking<Unit> {
fun `don't save if only format is changed`() = runBlocking<Unit>(Dispatchers.Default) {
val oldContent = """<application><component name="A" foo="old" deprecated="old"/></application>"""
val file = writeConfig("a.xml", oldContent)
val oldModificationTime = file.getLastModifiedTime()
@@ -280,15 +280,18 @@ class ApplicationStoreTest {
}
@Test
fun `loadState failed with exception it won't be called next time`() {
fun `loadState failed with exception it won't be called next time`() = runBlocking<Unit>(Dispatchers.Default) {
writeConfig("a.xml", """<application><component name="A" foo="old" deprecated="old"/></application>""")
testAppConfig.refreshVfs()
val component = A()
component.isThrowErrorOnLoadState = true
assertThatThrownBy {
try {
componentStore.initComponent(component, null, PluginManagerCore.CORE_ID)
}.isInstanceOf(ProcessCanceledException::class.java)
}
catch (e: Throwable) {
assertThat(e).isInstanceOf(ProcessCanceledException::class.java)
}
assertThat(component.options).isEqualTo(TestState())
component.isThrowErrorOnLoadState = false
@@ -420,7 +423,7 @@ class ApplicationStoreTest {
}
@Test
fun `survive on error`() {
fun `survive on error`() = runBlocking<Unit>(Dispatchers.Default) {
@State(name = "Bad", storages = [Storage(value = "foo.xml")])
class MyComponent : PersistentStateComponent<Foo> {
override fun loadState(state: Foo) {
@@ -437,16 +440,17 @@ class ApplicationStoreTest {
}
val component = MyComponent()
rethrowLoggedErrorsIn {
assertThatThrownBy {
componentStore.initComponent(component, null, PluginManagerCore.CORE_ID)
}.hasMessage("Cannot init component state (componentName=Bad, componentClass=MyComponent) [Plugin: com.intellij]")
try {
componentStore.initComponent(component, null, PluginManagerCore.CORE_ID)
}
catch (e: Throwable) {
assertThat(e.message).contains("Cannot init component state (componentName=Bad, componentClass=MyComponent) [Plugin: com.intellij]")
}
assertThat(componentStore.getComponents()).doesNotContainKey("Bad")
}
@Test
fun `test per-os components are stored in subfolder`() = runBlocking {
fun `test per-os components are stored in subfolder`() = runBlocking(Dispatchers.Default) {
val component = PerOsComponent()
componentStore.initComponent(component, null, PluginManagerCore.CORE_ID)
component.foo = "bar"
@@ -478,7 +482,7 @@ class ApplicationStoreTest {
}
@Test
fun `per-os setting is preferred from os subfolder`() {
fun `per-os setting is preferred from os subfolder`() = runBlocking<Unit>(Dispatchers.Default) {
val osCode = getPerOsSettingsStorageFolderName()
writeConfig("per-os.xml", "<application>${createComponentData("old")}</application>")
writeConfig("${osCode}/per-os.xml", "<application>${createComponentData("new")}</application>")
@@ -514,7 +518,7 @@ class ApplicationStoreTest {
}
@Test
fun `reload components`() {
fun `reload components`() = runBlocking<Unit>(Dispatchers.Default) {
@State(name = "A", storages = [Storage(value = "a.xml")])
class Component : FooComponent()
@@ -655,6 +659,10 @@ class ApplicationStoreTest {
// yes, in tests APP_CONFIG equals to ROOT_CONFIG (as ICS does)
storageManager.setMacros(listOf(Macro(APP_CONFIG, path), Macro(ROOT_CONFIG, path), Macro(StoragePathMacros.CACHE_FILE, path)))
}
override fun error(error: PluginException) {
throw error
}
}
private abstract class FooComponent : PersistentStateComponent<Foo> {

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.configurationStore
import com.intellij.configurationStore.schemeManager.ROOT_CONFIG

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.configurationStore
import com.intellij.ide.highlighter.ProjectFileType
@@ -6,8 +6,8 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ex.PathManagerEx
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.components.impl.stores.stateStore
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.project.ex.ProjectManagerEx
@@ -36,7 +36,7 @@ class DefaultProjectStoreTest {
@JvmField @Rule val fsRule = InMemoryFsRule()
@Test
fun `new project from default - file-based storage`() {
fun `new project from default - file-based storage`() = runBlocking(Dispatchers.Default) {
checkDefaultProjectAsTemplate { checkTask ->
val project = openAsNewProjectAndUseDefaultSettings(fsRule.fs.getPath("/test${ProjectFileType.DOT_DEFAULT_EXTENSION}"))
project.useProject {
@@ -46,7 +46,7 @@ class DefaultProjectStoreTest {
}
@Test
fun `new project from default - directory-based storage`() {
fun `new project from default - directory-based storage`() = runBlocking(Dispatchers.Default) {
checkDefaultProjectAsTemplate { checkTask ->
// obviously, the project must be directory-based also
val project = openAsNewProjectAndUseDefaultSettings(fsRule.fs.getPath("/test"))

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.configurationStore
import com.intellij.ide.highlighter.ProjectFileType
@@ -19,6 +19,7 @@ import com.intellij.project.stateStore
import com.intellij.testFramework.*
import com.intellij.testFramework.assertions.Assertions.assertThat
import com.intellij.util.PathUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.intellij.lang.annotations.Language
import org.junit.ClassRule
@@ -227,7 +228,7 @@ class ProjectStoreTest {
// heavy test that uses ProjectManagerImpl directly to test (opposite to DefaultProjectStoreTest)
@Test
fun `just created project must inherit settings from the default project`() {
fun `just created project must inherit settings from the default project`() = runBlocking<Unit>(Dispatchers.Default) {
val projectManager = ProjectManagerEx.getInstanceEx()
val testComponent = TestComponent()

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.project
import com.intellij.ide.CommandLineProcessor
@@ -10,6 +10,7 @@ import com.intellij.testFramework.*
import com.intellij.testFramework.assertions.Assertions.assertThat
import com.intellij.testFramework.rules.checkDefaultProjectAsTemplate
import com.intellij.util.io.createDirectories
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.ClassRule
import org.junit.Rule
@@ -52,7 +53,7 @@ internal class OpenProjectTest(private val opener: Opener) {
val disposableRule = DisposableRule()
@Test
fun `open valid existing project dir with ability to attach`() {
fun `open valid existing project dir with ability to attach`() = runBlocking(Dispatchers.Default) {
ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(ModuleAttachProcessor()), disposableRule.disposable)
val projectDir = tempDir.newPath("project")
projectDir.resolve(".idea").createDirectories()
@@ -60,7 +61,7 @@ internal class OpenProjectTest(private val opener: Opener) {
}
@Test
fun `open clean existing project dir with ability to attach`() {
fun `open clean existing project dir with ability to attach`() = runBlocking(Dispatchers.Default) {
ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(ModuleAttachProcessor()), disposableRule.disposable)
val projectDir = tempDir.newPath("project")
projectDir.createDirectories()
@@ -68,7 +69,7 @@ internal class OpenProjectTest(private val opener: Opener) {
}
@Test
fun `open valid existing project dir with inability to attach`() {
fun `open valid existing project dir with inability to attach`() = runBlocking(Dispatchers.Default) {
// Regardless of product (Idea vs PhpStorm), if .idea directory exists, but no modules, we must run configurators to add some module.
// Maybe not fully clear why it is performed as part of project opening and silently, but it is existing behaviour.
// So, existing behaviour should be preserved and any changes should be done not as part of task "use unified API to open project", but separately later.
@@ -79,14 +80,17 @@ internal class OpenProjectTest(private val opener: Opener) {
}
@Test
fun `open clean existing project dir with inability to attach`() {
fun `open clean existing project dir with inability to attach`() = runBlocking(Dispatchers.Default) {
ExtensionTestUtil.maskExtensions(ProjectAttachProcessor.EP_NAME, listOf(), disposableRule.disposable)
val projectDir = tempDir.newPath("project")
projectDir.createDirectories()
openUsingOpenFileActionAndAssertThatProjectContainsOneModule(projectDir, defaultProjectTemplateShouldBeApplied = true)
}
private fun openUsingOpenFileActionAndAssertThatProjectContainsOneModule(projectDir: Path, defaultProjectTemplateShouldBeApplied: Boolean) {
private suspend fun openUsingOpenFileActionAndAssertThatProjectContainsOneModule(
projectDir: Path,
defaultProjectTemplateShouldBeApplied: Boolean,
) {
checkDefaultProjectAsTemplate { checkDefaultProjectAsTemplateTask ->
val project = opener.opener(projectDir)!!
project.useProject {

View File

@@ -22,7 +22,9 @@ interface IComponentStore {
fun setPath(path: Path) {
}
fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId)
fun initComponentBlocking(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId)
suspend fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId)
fun unloadComponent(component: Any)

View File

@@ -612,17 +612,17 @@ abstract class ComponentManagerImpl(
}
}
internal fun initializeService(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
internal suspend fun initializeService(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
initializeService(component = component, serviceDescriptor = serviceDescriptor, pluginId = pluginId) {
it()
}
}
internal inline fun initializeService(
internal suspend inline fun initializeService(
component: Any,
serviceDescriptor: ServiceDescriptor?,
pluginId: PluginId,
invocator: (() -> Unit) -> Unit,
invocator: suspend (() -> Unit) -> Unit,
) {
@Suppress("DEPRECATION")
if ((serviceDescriptor == null || !isPreInitialized(component)) &&
@@ -633,7 +633,7 @@ abstract class ComponentManagerImpl(
}
invocator {
componentStore.initComponent(component = component, serviceDescriptor = serviceDescriptor, pluginId = pluginId)
componentStore.initComponentBlocking(component = component, serviceDescriptor = serviceDescriptor, pluginId = pluginId)
}
}
}

View File

@@ -43,7 +43,10 @@ private class TestComponentStore : IComponentStore {
override val storageManager: StateStorageManager
get() = TODO("not implemented")
override fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
override fun initComponentBlocking(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
}
override suspend fun initComponent(component: Any, serviceDescriptor: ServiceDescriptor?, pluginId: PluginId) {
}
override fun unloadComponent(component: Any) {

View File

@@ -13,6 +13,7 @@ jvm_library(
visibility = ["//visibility:public"],
srcs = glob(["src/**/*.kt", "src/**/*.java"], allow_empty = True),
deps = [
"@lib//:kotlin-stdlib",
"//platform/analysis-api:analysis",
"//platform/util/concurrency",
"//platform/diff-api:diff",
@@ -57,6 +58,7 @@ jvm_library(
srcs = glob(["tests/**/*.kt", "tests/**/*.java"], allow_empty = True),
associates = [":settingsSync-core"],
deps = [
"@lib//:kotlin-stdlib",
"//platform/analysis-api:analysis",
"//platform/util/concurrency",
"//platform/util/concurrency:concurrency_test_lib",
@@ -106,6 +108,7 @@ jvm_library(
"//platform/vcs-impl",
"//platform/vcs-impl:vcs-impl_test_lib",
"//platform/diff-impl",
"@lib//:assert_j",
],
runtime_deps = [":settingsSync-core_resources"]
)

View File

@@ -30,6 +30,7 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.platform.analysis" />
<orderEntry type="module" module-name="intellij.platform.concurrency" />
<orderEntry type="module" module-name="intellij.platform.diff" />
@@ -69,5 +70,6 @@
<orderEntry type="module" module-name="intellij.platform.vcs.log" />
<orderEntry type="module" module-name="intellij.platform.vcs.impl" />
<orderEntry type="module" module-name="intellij.platform.diff.impl" />
<orderEntry type="library" scope="TEST" name="assertJ" level="project" />
</component>
</module>

View File

@@ -195,7 +195,7 @@ internal class SettingsSyncIdeMediatorImpl(private val componentStore: Component
if (!folder.exists()) return true
Files.walkFileTree(folder, object : SimpleFileVisitor<Path>() {
override fun visitFile(file: Path, attrs: BasicFileAttributes?): FileVisitResult {
override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
if (!filter(file.name)) return FileVisitResult.CONTINUE
if (!file.isRegularFile()) return FileVisitResult.CONTINUE

View File

@@ -5,7 +5,6 @@ import com.intellij.testFramework.common.timeoutRunBlocking
import com.intellij.testFramework.common.waitUntil
import com.intellij.testFramework.registerExtension
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
@@ -27,7 +26,7 @@ internal class SettingsProviderTest : SettingsSyncRealIdeTestBase() {
fun `settings from provider should be collected`() = timeoutRunBlockingAndStopBridge {
val ideState = TestState("IDE value")
settingsProvider.settings = ideState
GeneralSettings.getInstance().initModifyAndSave {
initModifyAndSave(GeneralSettings.getInstance()) {
autoSaveFiles = false
}

View File

@@ -10,12 +10,15 @@ import com.intellij.openapi.components.*
import com.intellij.openapi.components.impl.stores.stateStore
import com.intellij.testFramework.fixtures.BasePlatformTestCase
import com.intellij.testFramework.rules.InMemoryFsRule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScheduler
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.assertj.core.api.Assertions.assertThat
import org.junit.Assert
import org.junit.Rule
import org.junit.Test
@@ -25,14 +28,12 @@ import java.nio.file.Path
import java.time.Instant
import kotlin.io.path.createDirectories
import kotlin.io.path.createFile
import kotlin.io.path.div
import kotlin.io.path.pathString
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(JUnit4::class)
class SettingsSyncIdeMediatorTest : BasePlatformTestCase() {
protected lateinit var testScope: TestScope
private lateinit var testScope: TestScope
override fun setUp() {
super.setUp()
@@ -44,7 +45,7 @@ class SettingsSyncIdeMediatorTest : BasePlatformTestCase() {
@Test
fun `process children with subfolders`() {
val rootConfig = memoryFs.fs.getPath("/appconfig")
val rootConfig = memoryFs.fs.getPath("/appConfig")
val componentStore = object : ComponentStoreImpl() {
override val storageManager: StateStorageManager
get() = TODO("Not yet implemented")
@@ -55,17 +56,22 @@ class SettingsSyncIdeMediatorTest : BasePlatformTestCase() {
}
val mediator = SettingsSyncIdeMediatorImpl(componentStore, rootConfig, {true})
val fileTypes = rootConfig / "filetypes"
val code = (fileTypes / "code").createDirectories()
(code / "mytemplate.kt").createFile()
val fileTypes = rootConfig.resolve("filetypes")
val code = fileTypes.resolve("code").createDirectories()
code.resolve("myTemplate.kt").createFile()
val visited = mutableSetOf<String>()
mediator.processChildren(fileTypes.pathString, RoamingType.DEFAULT, {true}, processor = { name, _, _ ->
visited += name
true
})
val visited = HashSet<String>()
mediator.processChildren(
path = fileTypes.pathString,
roamingType = RoamingType.DEFAULT,
filter = { true },
processor = { name, _, _ ->
visited += name
true
},
)
assertEquals(setOf("mytemplate.kt"), visited)
assertThat(visited).containsExactlyInAnyOrder("myTemplate.kt")
}
@Test
@@ -133,12 +139,16 @@ true
val callbackCalls = mutableListOf<String>()
val firstComponent = FirstComponent({ callbackCalls.add("First") })
componentManager.registerComponentInstance(FirstComponent::class.java, firstComponent)
componentStore.initComponent(firstComponent, null, PluginManagerCore.CORE_ID)
runBlocking(Dispatchers.Default) {
componentStore.initComponent(firstComponent, null, PluginManagerCore.CORE_ID)
}
componentStore.storageManager.getStateStorage(getStateSpec(FirstComponent::class.java)!!.storages[0]).createSaveSessionProducer()
val secondComponent = SecondComponent({ callbackCalls.add("Second") })
componentManager.registerComponentInstance(SecondComponent::class.java, secondComponent)
componentStore.initComponent(secondComponent, null, PluginManagerCore.CORE_ID)
runBlocking(Dispatchers.Default) {
componentStore.initComponent(secondComponent, null, PluginManagerCore.CORE_ID)
}
componentStore.storageManager.getStateStorage(getStateSpec(SecondComponent::class.java)!!.storages[0]).createSaveSessionProducer()
val mediator = SettingsSyncIdeMediatorImpl(componentStore = componentStore, rootConfig = rootConfig) { true }

View File

@@ -6,7 +6,8 @@ import com.intellij.ide.fileTemplates.FileTemplateManager
import com.intellij.ide.ui.UISettings
import com.intellij.idea.TestFor
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.*
import com.intellij.openapi.components.SettingsCategory
import com.intellij.openapi.components.State
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
import com.intellij.openapi.keymap.impl.KeymapImpl
import com.intellij.openapi.keymap.impl.KeymapManagerImpl
@@ -14,6 +15,7 @@ import com.intellij.openapi.util.Disposer
import com.intellij.settingsSync.core.SettingsSnapshot.MetaInfo
import com.intellij.util.toByteArray
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
@@ -28,14 +30,16 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `settings are pushed`() = timeoutRunBlockingAndStopBridge {
SettingsSyncSettings.getInstance().init()
init(SettingsSyncSettings.getInstance())
SettingsSyncSettings.getInstance().migrationFromOldStorageChecked = true
saveComponentStore()
componentStore.save()
initSettingsSync(SettingsSyncBridge.InitMode.PushToServer)
executeAndWaitUntilPushed {
GeneralSettings.getInstance().initModifyAndSave {
autoSaveFiles = false
runBlocking(Dispatchers.Default) {
initModifyAndSave(GeneralSettings.getInstance()) {
autoSaveFiles = false
}
}
}
@@ -55,7 +59,7 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
val keymap = createKeymap()
executeAndWaitUntilPushed {
saveComponentStore()
runBlocking(Dispatchers.IO) { componentStore.save() }
}
assertServerSnapshot {
@@ -76,7 +80,7 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
fileTemplate.text= phpCode
FileTemplateManager.getDefaultInstance().saveAllTemplates()
executeAndWaitUntilPushed {
saveComponentStore()
runBlocking(Dispatchers.IO) { componentStore.save() }
}
assertServerSnapshot {
@@ -95,18 +99,14 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
return keymap
}
private fun saveComponentStore() {
runBlocking { componentStore.save() }
}
@Test
fun `quickly modified settings are pushed together`() = timeoutRunBlockingAndStopBridge {
initSettingsSync(SettingsSyncBridge.InitMode.JustInit)
GeneralSettings.getInstance().initModifyAndSave {
initModifyAndSave(GeneralSettings.getInstance()) {
autoSaveFiles = false
}
EditorSettingsExternalizable.getInstance().initModifyAndSave {
initModifyAndSave(EditorSettingsExternalizable.getInstance()) {
SHOW_INTENTION_BULB = false
}
@@ -128,15 +128,17 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `existing settings are copied on initialization`() = timeoutRunBlockingAndStopBridge {
GeneralSettings.getInstance().initModifyAndSave {
initModifyAndSave(GeneralSettings.getInstance()) {
autoSaveFiles = false
}
initSettingsSync(SettingsSyncBridge.InitMode.JustInit)
executeAndWaitUntilPushed {
UISettings.getInstance().initModifyAndSave {
recentFilesLimit = 1000
runBlocking(Dispatchers.Default) {
initModifyAndSave(UISettings.getInstance()) {
recentFilesLimit = 1000
}
}
}
@@ -156,17 +158,17 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `disabled categories should be ignored when copying settings on initialization`() = timeoutRunBlockingAndStopBridge {
GeneralSettings.getInstance().initModifyAndSave {
initModifyAndSave(GeneralSettings.getInstance()) {
autoSaveFiles = false
}
EditorSettingsExternalizable.getInstance().initModifyAndSave {
initModifyAndSave(EditorSettingsExternalizable.getInstance()) {
SHOW_INTENTION_BULB = false
}
//AppEditorFontOptions.getInstance().initModifyAndSave {
// FONT_SIZE = FontPreferences.DEFAULT_FONT_SIZE - 5
//}
val keymap = createKeymap()
saveComponentStore()
componentStore.save()
val os = getPerOsSettingsStorageFolderName()
SettingsSyncSettings.getInstance().setCategoryEnabled(SettingsCategory.KEYMAP, false)
@@ -191,7 +193,7 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `settings from server are applied`() = timeoutRunBlockingAndStopBridge(5.seconds) {
val generalSettings = GeneralSettings.getInstance().init()
val generalSettings = init(GeneralSettings.getInstance())
initSettingsSync(SettingsSyncBridge.InitMode.JustInit)
val fileState = GeneralSettings().apply {
@@ -210,10 +212,10 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `enabling category should copy existing settings from that category`() = timeoutRunBlockingAndStopBridge {
SettingsSyncSettings.getInstance().setCategoryEnabled(SettingsCategory.CODE, isEnabled = false)
GeneralSettings.getInstance().initModifyAndSave {
initModifyAndSave(GeneralSettings.getInstance()) {
autoSaveFiles = false
}
EditorSettingsExternalizable.getInstance().initModifyAndSave {
initModifyAndSave(EditorSettingsExternalizable.getInstance()) {
SHOW_INTENTION_BULB = false
}
val editorXmlContent = (configDir / "options" / "editor.xml").readText()
@@ -249,8 +251,8 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `not enabling cross IDE sync initially works as expected`() = timeoutRunBlockingAndStopBridge {
SettingsSyncSettings.getInstance().init()
GeneralSettings.getInstance().initModifyAndSave { autoSaveFiles = false }
init(SettingsSyncSettings.getInstance())
initModifyAndSave(GeneralSettings.getInstance()) { autoSaveFiles = false }
assertIdeCrossSync(false)
@@ -270,8 +272,8 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `enabling cross IDE sync initially works as expected`() = timeoutRunBlockingAndStopBridge {
SettingsSyncSettings.getInstance().init()
GeneralSettings.getInstance().initModifyAndSave { autoSaveFiles = false }
init(SettingsSyncSettings.getInstance())
initModifyAndSave(GeneralSettings.getInstance()) { autoSaveFiles = false }
assertIdeCrossSync(false)
@@ -291,8 +293,8 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `sync settings are always uploaded even if system settings are disabled`() = timeoutRunBlockingAndStopBridge {
SettingsSyncSettings.getInstance().init()
GeneralSettings.getInstance().initModifyAndSave { autoSaveFiles = false }
init(SettingsSyncSettings.getInstance())
initModifyAndSave(GeneralSettings.getInstance()) { autoSaveFiles = false }
SettingsSyncSettings.getInstance().setCategoryEnabled(SettingsCategory.SYSTEM, false)
@@ -322,8 +324,8 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
fun `don't sync non-roamable files`() = timeoutRunBlockingAndStopBridge {
val nonRoamable = ExportableNonRoamable()
nonRoamable.init()
val generalSettings = GeneralSettings.getInstance().init()
init(nonRoamable)
val generalSettings = init(GeneralSettings.getInstance())
initSettingsSync(SettingsSyncBridge.InitMode.JustInit)
val generalSettingsState = GeneralSettings().apply {
@@ -381,11 +383,11 @@ internal class SettingsSyncRealIdeTest : SettingsSyncRealIdeTestBase() {
@Test
fun `local and remote changes in different files are both applied`() = timeoutRunBlockingAndStopBridge {
val generalSettings = GeneralSettings.getInstance().init()
val generalSettings = init(GeneralSettings.getInstance())
initSettingsSync(SettingsSyncBridge.InitMode.JustInit)
// prepare local commit but don't allow it to be pushed
UISettings.getInstance().initModifyAndSave {
initModifyAndSave(UISettings.getInstance()) {
compactTreeIndents = true
}
// at this point there is an unpushed local commit

View File

@@ -39,7 +39,7 @@ internal abstract class SettingsSyncRealIdeTestBase : SettingsSyncTestBase() {
application.registerService(Roamable::class.java, Roamable::class.java, testPluginDescriptor, false)
//application.registerService(Roamable::class.java, Roamable::class.java, false)
application.processAllImplementationClasses { componentClass, plugin ->
application.processAllImplementationClasses { _, _ ->
// do nothing
}
}
@@ -76,7 +76,7 @@ internal abstract class SettingsSyncRealIdeTestBase : SettingsSyncTestBase() {
protected fun CoroutineScope.initSettingsSync(initMode: SettingsSyncBridge.InitMode = SettingsSyncBridge.InitMode.JustInit, crossIdeSync: Boolean = false) {
SettingsSyncSettings.getInstance().syncEnabled = true
SettingsSyncLocalSettings.getInstance().state.crossIdeSyncEnabled = crossIdeSync;
SettingsSyncLocalSettings.getInstance().state.crossIdeSyncEnabled = crossIdeSync
val ideMediator = SettingsSyncIdeMediatorImpl(componentStore, configDir, enabledCondition = { true })
val controls = SettingsSyncMain.init(this, disposable, settingsSyncStorage, configDir, ideMediator)
updateChecker = controls.updateChecker
@@ -105,21 +105,22 @@ internal abstract class SettingsSyncRealIdeTestBase : SettingsSyncTestBase() {
}
}
protected fun <T : PersistentStateComponent<*>> T.init(): T {
componentStore.initComponent(component = this, serviceDescriptor = null, pluginId = PluginManagerCore.CORE_ID)
val defaultConstructor: Constructor<T> = this::class.java.declaredConstructors.find { it.parameterCount == 0 } as Constructor<T>
protected suspend fun <T : PersistentStateComponent<*>> init(component: T): T {
componentStore.initComponent(component = component, serviceDescriptor = null, pluginId = PluginManagerCore.CORE_ID)
@Suppress("UNCHECKED_CAST")
val defaultConstructor: Constructor<T> = component::class.java.declaredConstructors.find { it.parameterCount == 0 } as Constructor<T>
val componentInstance: T = defaultConstructor.newInstance()
componentStore.componentsAndDefaultStates[this] = componentInstance.state!!
return this
componentStore.componentsAndDefaultStates[component] = componentInstance.state!!
return component
}
protected fun <State, Component : PersistentStateComponent<State>> Component.initModifyAndSave(modifier: State.() -> Unit): Component {
this.init()
this.state!!.modifier()
protected suspend fun <State, Component : PersistentStateComponent<State>> initModifyAndSave(component: Component, modifier: State.() -> Unit): Component {
init(component)
component.state!!.modifier()
runBlocking {
componentStore.save()
}
return this
return component
}
protected fun <State, Component : PersistentStateComponent<State>> Component.withState(stateApplier: State.() -> Unit): Component {
@@ -150,8 +151,8 @@ internal abstract class SettingsSyncRealIdeTestBase : SettingsSyncTestBase() {
fun resetComponents() {
for ((component, defaultState) in componentsAndDefaultStates) {
val c = component as PersistentStateComponent<Any>
c.loadState(defaultState)
@Suppress("UNCHECKED_CAST")
(component as PersistentStateComponent<Any>).loadState(defaultState)
}
}
@@ -160,7 +161,7 @@ internal abstract class SettingsSyncRealIdeTestBase : SettingsSyncTestBase() {
companion object {
@JvmStatic
@BeforeAll
fun warmUp(): Unit {
fun warmUp() {
val tempDir = createTempDirectory("gitWarmup-${System.currentTimeMillis()}", "beforeAll")
val parentDisposable = Disposer.newDisposable()
val gitSettingsLog = GitSettingsLog(

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.testFramework.rules
import com.intellij.ide.plugins.PluginManagerCore
@@ -28,7 +28,9 @@ private class TestComponent : SimplePersistentStateComponent<TestComponent.TestC
}
@ApiStatus.Internal
fun checkDefaultProjectAsTemplate(task: (checkTask: (project: Project, defaultProjectTemplateShouldBeApplied: Boolean) -> Unit) -> Unit) {
suspend fun checkDefaultProjectAsTemplate(
task: suspend (checkTask: suspend (project: Project, defaultProjectTemplateShouldBeApplied: Boolean) -> Unit) -> Unit,
) {
val defaultTestComponent = TestComponent()
val defaultStateStore = ProjectManager.getInstance().defaultProject.service<IComponentStore>()
defaultStateStore.initComponent(component = defaultTestComponent, serviceDescriptor = null, pluginId = PluginManagerCore.CORE_ID)
@@ -50,6 +52,7 @@ fun checkDefaultProjectAsTemplate(task: (checkTask: (project: Project, defaultPr
}
finally {
// clear state
@Suppress("TestOnlyProblems")
defaultStateStore.removeComponent(TEST_COMPONENT_NAME)
}
}

View File

@@ -329,7 +329,7 @@ inline fun <T> Project.runInLoadComponentStateMode(task: () -> T): T {
/**
* Closes a project after [action].
*/
fun <T> Project.useProject(save: Boolean = false, action: (Project) -> T): T {
inline fun <T> Project.useProject(save: Boolean = false, action: (Project) -> T): T {
try {
return action(this)
}
@@ -394,7 +394,8 @@ private inline fun <R> closeOpenedProjectsIfFailImpl(closeProject: Project.() ->
}
}
private fun Project.closeProject(save: Boolean = false) {
@PublishedApi
internal fun Project.closeProject(save: Boolean = false) {
invokeAndWaitIfNeeded {
if (save) {
saveWorkspaceModel()

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.settingsRepository.test
import com.intellij.configurationStore.ApplicationStoreImpl
@@ -140,10 +140,10 @@ class LoadTest : LoadTestBase() {
val keymapXml = repositoryDir.resolve("keymap.xml")
keymapXml.write(content)
val component = SeveralStoragesConfigured()
componentStore.initComponent(component, null, PluginManagerCore.CORE_ID)
component.flag = true
runBlocking {
val component = SeveralStoragesConfigured()
componentStore.initComponent(component = component, serviceDescriptor = null, pluginId = PluginManagerCore.CORE_ID)
component.flag = true
componentStore.save(true)
}