From 1976bac341239e4d8a512ca2eab9e629e40381f2 Mon Sep 17 00:00:00 2001 From: Ilya Korennoy Date: Tue, 22 Jul 2025 13:02:36 +0200 Subject: [PATCH] IJPL-197985: Add test for WorkspaceFileIndexListener GitOrigin-RevId: 3ff0141d5be4e290dfc4f67f93f54be79450a099 --- .../testEntities/impl/ChildTestEntityImpl.kt | 1 + .../testEntities/impl/MetadataStorageImpl.kt | 5 +- .../testEntities/impl/ParentTestEntityImpl.kt | 30 ++++- .../testEntities/impl/SiblingEntityImpl.kt | 1 + .../testEntities/ParentChildTestEntities.kt | 5 + ...aceFileIndexContributorDependenciesTest.kt | 10 +- .../WorkspaceFileIndexListenerTest.kt | 125 ++++++++++++++++++ 7 files changed, 172 insertions(+), 5 deletions(-) create mode 100644 platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexListenerTest.kt diff --git a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ChildTestEntityImpl.kt b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ChildTestEntityImpl.kt index 54a59cab87de..ea60483c75c3 100644 --- a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ChildTestEntityImpl.kt +++ b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ChildTestEntityImpl.kt @@ -20,6 +20,7 @@ import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInst import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata +import com.intellij.platform.workspace.storage.url.VirtualFileUrl import com.intellij.util.indexing.testEntities.ChildTestEntity import com.intellij.util.indexing.testEntities.ParentTestEntity diff --git a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/MetadataStorageImpl.kt b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/MetadataStorageImpl.kt index 289feaa9f023..220c7c651b52 100644 --- a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/MetadataStorageImpl.kt +++ b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/MetadataStorageImpl.kt @@ -47,7 +47,8 @@ OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = typeMetadata = EntityMetadata(fqName = "com.intellij.util.indexing.testEntities.ParentTestEntity", entityDataFqName = "com.intellij.util.indexing.testEntities.impl.ParentTestEntityData", supertypes = listOf("com.intellij.platform.workspace.storage.WorkspaceEntity"), properties = listOf(OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "entitySource", valueType = ValueTypeMetadata.SimpleType.CustomType(isNullable = false, typeMetadata = FinalClassMetadata.KnownClass(fqName = "com.intellij.platform.workspace.storage.EntitySource")), withDefault = false), OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "child", valueType = ValueTypeMetadata.EntityReference(connectionType = ConnectionId.ConnectionType.ONE_TO_ONE, entityFqName = "com.intellij.util.indexing.testEntities.ChildTestEntity", isChild = true, isNullable = true), withDefault = false), OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "secondChild", valueType = ValueTypeMetadata.EntityReference(connectionType = ConnectionId.ConnectionType.ONE_TO_ONE, entityFqName = "com.intellij.util.indexing.testEntities.SiblingEntity", isChild = true, isNullable = true), withDefault = false), -OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "customParentProperty", valueType = primitiveTypeStringNotNullable, withDefault = false)), extProperties = listOf(), isAbstract = false) +OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "customParentProperty", valueType = primitiveTypeStringNotNullable, withDefault = false), +OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = "parentEntityRoot", valueType = ValueTypeMetadata.SimpleType.CustomType(isNullable = false, typeMetadata = FinalClassMetadata.KnownClass(fqName = "com.intellij.platform.workspace.storage.url.VirtualFileUrl")), withDefault = false)), extProperties = listOf(), isAbstract = false) addMetadata(typeMetadata) @@ -63,7 +64,7 @@ OwnPropertyMetadata(isComputable = false, isKey = false, isOpen = false, name = addMetadataHash(typeFqn = "com.intellij.util.indexing.testEntities.IndexingTestEntity", metadataHash = -233870197) addMetadataHash(typeFqn = "com.intellij.util.indexing.testEntities.NonIndexableTestEntity", metadataHash = -15259978) addMetadataHash(typeFqn = "com.intellij.util.indexing.testEntities.NonRecursiveTestEntity", metadataHash = -55418454) - addMetadataHash(typeFqn = "com.intellij.util.indexing.testEntities.ParentTestEntity", metadataHash = -1345180662) + addMetadataHash(typeFqn = "com.intellij.util.indexing.testEntities.ParentTestEntity", metadataHash = 1278429366) addMetadataHash(typeFqn = "com.intellij.util.indexing.testEntities.SiblingEntity", metadataHash = 144691097) addMetadataHash(typeFqn = "com.intellij.platform.workspace.storage.EntitySource", metadataHash = 1279624819) addMetadataHash(typeFqn = "com.intellij.util.indexing.testEntities.TestModuleEntitySource", metadataHash = -1815413801) diff --git a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ParentTestEntityImpl.kt b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ParentTestEntityImpl.kt index 04f4a141d077..81325433af15 100644 --- a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ParentTestEntityImpl.kt +++ b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/ParentTestEntityImpl.kt @@ -20,6 +20,7 @@ import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInst import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata +import com.intellij.platform.workspace.storage.url.VirtualFileUrl import com.intellij.util.indexing.testEntities.ChildTestEntity import com.intellij.util.indexing.testEntities.ParentTestEntity import com.intellij.util.indexing.testEntities.SiblingEntity @@ -54,6 +55,12 @@ internal class ParentTestEntityImpl(private val dataSource: ParentTestEntityData return dataSource.customParentProperty } + override val parentEntityRoot: VirtualFileUrl + get() { + readField("parentEntityRoot") + return dataSource.parentEntityRoot + } + override val entitySource: EntitySource get() { readField("entitySource") @@ -87,6 +94,7 @@ internal class ParentTestEntityImpl(private val dataSource: ParentTestEntityData // Builder may switch to snapshot at any moment and lock entity data to modification this.currentEntityData = null + index(this, "parentEntityRoot", this.parentEntityRoot) // Process linked entities that are connected without a builder processLinkedEntities(builder) checkInitialization() // TODO uncomment and check failed tests @@ -100,6 +108,9 @@ internal class ParentTestEntityImpl(private val dataSource: ParentTestEntityData if (!getEntityData().isCustomParentPropertyInitialized()) { error("Field ParentTestEntity#customParentProperty should be initialized") } + if (!getEntityData().isParentEntityRootInitialized()) { + error("Field ParentTestEntity#parentEntityRoot should be initialized") + } } override fun connectionIdList(): List { @@ -111,6 +122,7 @@ internal class ParentTestEntityImpl(private val dataSource: ParentTestEntityData dataSource as ParentTestEntity if (this.entitySource != dataSource.entitySource) this.entitySource = dataSource.entitySource if (this.customParentProperty != dataSource.customParentProperty) this.customParentProperty = dataSource.customParentProperty + if (this.parentEntityRoot != dataSource.parentEntityRoot) this.parentEntityRoot = dataSource.parentEntityRoot updateChildToParentReferences(parents) } @@ -204,6 +216,16 @@ internal class ParentTestEntityImpl(private val dataSource: ParentTestEntityData changedProperty.add("customParentProperty") } + override var parentEntityRoot: VirtualFileUrl + get() = getEntityData().parentEntityRoot + set(value) { + checkModificationAllowed() + getEntityData(true).parentEntityRoot = value + changedProperty.add("parentEntityRoot") + val _diff = diff + if (_diff != null) index(this, "parentEntityRoot", value) + } + override fun getEntityClass(): Class = ParentTestEntity::class.java } } @@ -211,8 +233,10 @@ internal class ParentTestEntityImpl(private val dataSource: ParentTestEntityData @OptIn(WorkspaceEntityInternalApi::class) internal class ParentTestEntityData : WorkspaceEntityData() { lateinit var customParentProperty: String + lateinit var parentEntityRoot: VirtualFileUrl internal fun isCustomParentPropertyInitialized(): Boolean = ::customParentProperty.isInitialized + internal fun isParentEntityRootInitialized(): Boolean = ::parentEntityRoot.isInitialized override fun wrapAsModifiable(diff: MutableEntityStorage): WorkspaceEntity.Builder { val modifiable = ParentTestEntityImpl.Builder(null) @@ -241,7 +265,7 @@ internal class ParentTestEntityData : WorkspaceEntityData() { } override fun createDetachedEntity(parents: List>): WorkspaceEntity.Builder<*> { - return ParentTestEntity(customParentProperty, entitySource) { + return ParentTestEntity(customParentProperty, parentEntityRoot, entitySource) { } } @@ -258,6 +282,7 @@ internal class ParentTestEntityData : WorkspaceEntityData() { if (this.entitySource != other.entitySource) return false if (this.customParentProperty != other.customParentProperty) return false + if (this.parentEntityRoot != other.parentEntityRoot) return false return true } @@ -268,18 +293,21 @@ internal class ParentTestEntityData : WorkspaceEntityData() { other as ParentTestEntityData if (this.customParentProperty != other.customParentProperty) return false + if (this.parentEntityRoot != other.parentEntityRoot) return false return true } override fun hashCode(): Int { var result = entitySource.hashCode() result = 31 * result + customParentProperty.hashCode() + result = 31 * result + parentEntityRoot.hashCode() return result } override fun hashCodeIgnoringEntitySource(): Int { var result = javaClass.hashCode() result = 31 * result + customParentProperty.hashCode() + result = 31 * result + parentEntityRoot.hashCode() return result } } diff --git a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/SiblingEntityImpl.kt b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/SiblingEntityImpl.kt index c2d09ab7d4f2..0200c17c8d43 100644 --- a/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/SiblingEntityImpl.kt +++ b/platform/lang-impl/testEntities/testGen/com/intellij/util/indexing/testEntities/impl/SiblingEntityImpl.kt @@ -20,6 +20,7 @@ import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInst import com.intellij.platform.workspace.storage.instrumentation.EntityStorageInstrumentationApi import com.intellij.platform.workspace.storage.instrumentation.MutableEntityStorageInstrumentation import com.intellij.platform.workspace.storage.metadata.model.EntityMetadata +import com.intellij.platform.workspace.storage.url.VirtualFileUrl import com.intellij.util.indexing.testEntities.ParentTestEntity import com.intellij.util.indexing.testEntities.SiblingEntity diff --git a/platform/lang-impl/testEntities/testSrc/com/intellij/util/indexing/testEntities/ParentChildTestEntities.kt b/platform/lang-impl/testEntities/testSrc/com/intellij/util/indexing/testEntities/ParentChildTestEntities.kt index 1fbbb6e55894..0aa028112a8c 100644 --- a/platform/lang-impl/testEntities/testSrc/com/intellij/util/indexing/testEntities/ParentChildTestEntities.kt +++ b/platform/lang-impl/testEntities/testSrc/com/intellij/util/indexing/testEntities/ParentChildTestEntities.kt @@ -7,12 +7,14 @@ import com.intellij.platform.workspace.storage.GeneratedCodeApiVersion import com.intellij.platform.workspace.storage.MutableEntityStorage import com.intellij.platform.workspace.storage.WorkspaceEntity import com.intellij.platform.workspace.storage.annotations.Parent +import com.intellij.platform.workspace.storage.url.VirtualFileUrl interface ParentTestEntity : WorkspaceEntity { val child: ChildTestEntity? val secondChild: SiblingEntity? val customParentProperty: String + val parentEntityRoot: VirtualFileUrl //region generated code @GeneratedCodeApiVersion(3) @@ -21,6 +23,7 @@ interface ParentTestEntity : WorkspaceEntity { var child: ChildTestEntity.Builder? var secondChild: SiblingEntity.Builder? var customParentProperty: String + var parentEntityRoot: VirtualFileUrl } companion object : EntityType() { @@ -29,11 +32,13 @@ interface ParentTestEntity : WorkspaceEntity { @JvmName("create") operator fun invoke( customParentProperty: String, + parentEntityRoot: VirtualFileUrl, entitySource: EntitySource, init: (Builder.() -> Unit)? = null, ): Builder { val builder = builder() builder.customParentProperty = customParentProperty + builder.parentEntityRoot = parentEntityRoot builder.entitySource = entitySource init?.invoke(builder) return builder diff --git a/platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexContributorDependenciesTest.kt b/platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexContributorDependenciesTest.kt index 98b6b02b3fd6..901e691c740d 100644 --- a/platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexContributorDependenciesTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexContributorDependenciesTest.kt @@ -7,6 +7,7 @@ import com.intellij.openapi.module.Module import com.intellij.openapi.roots.SkipAddingToWatchedRoots import com.intellij.openapi.vfs.VirtualFile import com.intellij.platform.backend.workspace.WorkspaceModel +import com.intellij.platform.backend.workspace.toVirtualFileUrl import com.intellij.platform.workspace.storage.EntityStorage import com.intellij.testFramework.junit5.TestApplication import com.intellij.testFramework.junit5.TestDisposable @@ -54,7 +55,9 @@ class WorkspaceFileIndexContributorDependenciesTest { runBlocking { readAction { WorkspaceFileIndex.getInstance(projectModel.project).isInWorkspace(customContentFileSetRoot) } } // initialize model val model = WorkspaceModel.getInstance(projectModel.project) - val parent = ParentTestEntity("parent property", NonPersistentEntitySource) + val parent = ParentTestEntity("parent property", + customContentFileSetRoot.toVirtualFileUrl(model.getVirtualFileUrlManager()), + NonPersistentEntitySource) .also { it.child = ChildTestEntity("child property", NonPersistentEntitySource) } .also { it.secondChild = SiblingEntity("sibling property", NonPersistentEntitySource) } @@ -132,7 +135,10 @@ class WorkspaceFileIndexContributorDependenciesTest { @Test fun `child contributor should be called after its relative added`() = runBlocking { val model = WorkspaceModel.getInstance(projectModel.project) - val newParentEntity = ParentTestEntity("new parent property", NonPersistentEntitySource) { + + val newParentEntity = ParentTestEntity("new parent property", + customContentFileSetRoot.toVirtualFileUrl(model.getVirtualFileUrlManager()), + NonPersistentEntitySource) { child = ChildTestEntity("new child property", NonPersistentEntitySource) } diff --git a/platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexListenerTest.kt b/platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexListenerTest.kt new file mode 100644 index 000000000000..8e5a029924f1 --- /dev/null +++ b/platform/platform-tests/testSrc/com/intellij/workspaceModel/core/fileIndex/WorkspaceFileIndexListenerTest.kt @@ -0,0 +1,125 @@ +// 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.workspaceModel.core.fileIndex + +import com.intellij.concurrency.ConcurrentCollectionFactory +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.readAction +import com.intellij.openapi.module.Module +import com.intellij.openapi.roots.SkipAddingToWatchedRoots +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.platform.backend.workspace.WorkspaceModel +import com.intellij.platform.backend.workspace.toVirtualFileUrl +import com.intellij.platform.workspace.storage.EntityStorage +import com.intellij.testFramework.junit5.TestApplication +import com.intellij.testFramework.junit5.TestDisposable +import com.intellij.testFramework.rules.ProjectModelExtension +import com.intellij.util.indexing.testEntities.ParentTestEntity +import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexImpl +import com.intellij.workspaceModel.ide.NonPersistentEntitySource +import io.kotest.common.runBlocking +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import kotlin.test.assertEquals + +@TestApplication +class WorkspaceFileIndexListenerTest { + + @JvmField + @RegisterExtension + val projectModel: ProjectModelExtension = ProjectModelExtension() + + private val parentWorkspaceFileIndexContributor = ParentWorkspaceFileIndexContributor() + + @TestDisposable + private lateinit var disposable: Disposable + private lateinit var customContentFileSetRoot: VirtualFile + private lateinit var module: Module + + @BeforeEach + fun setUp() { + WorkspaceFileIndexImpl.EP_NAME.point.registerExtension(parentWorkspaceFileIndexContributor, disposable) + customContentFileSetRoot = projectModel.baseProjectDir.newVirtualDirectory("root") + module = projectModel.createModule() + + + runBlocking { readAction { WorkspaceFileIndex.getInstance(projectModel.project).isInWorkspace(customContentFileSetRoot) } } + } + + + @Test + fun `listener event for registered file set`() = runBlocking { + val listener = MyWorkspaceFileIndexListener() + projectModel.project.messageBus.connect().subscribe(WorkspaceFileIndexListener.TOPIC, listener) + + val model = WorkspaceModel.getInstance(projectModel.project) + + val parentEntityRoot = customContentFileSetRoot.toVirtualFileUrl(model.getVirtualFileUrlManager()) + val parent = ParentTestEntity("parent property", + parentEntityRoot, + NonPersistentEntitySource) + + model.update("Add entity") { + it.addEntity(parent) + } + + assertEquals(0, listener.removedRoots.size) + assertEquals(1, listener.storedRoots.size) + + assertEquals(parentEntityRoot, + listener.storedRoots.first().root.toVirtualFileUrl(model.getVirtualFileUrlManager())) + } + + + @Test + fun `listener event for removed file set`() = runBlocking { + val listener = MyWorkspaceFileIndexListener() + projectModel.project.messageBus.connect().subscribe(WorkspaceFileIndexListener.TOPIC, listener) + + val model = WorkspaceModel.getInstance(projectModel.project) + + val parentEntityRoot = customContentFileSetRoot.toVirtualFileUrl(model.getVirtualFileUrlManager()) + val parent = ParentTestEntity("parent property", + parentEntityRoot, + NonPersistentEntitySource) + + model.update("Add parent") { + it.addEntity(parent) + } + assertEquals(0, listener.removedRoots.size) + assertEquals(1, listener.storedRoots.size) + + val parentEntity = model.currentSnapshot.entities(ParentTestEntity::class.java).first() + + model.update("Remove entity") {storage -> + storage.removeEntity(parentEntity) + } + + assertEquals(1, listener.removedRoots.size) + assertEquals(1, listener.storedRoots.size) + assertEquals(parentEntityRoot, + listener.removedRoots.first().root.toVirtualFileUrl(model.getVirtualFileUrlManager())) + + } + + private class MyWorkspaceFileIndexListener : WorkspaceFileIndexListener { + val storedRoots = ConcurrentCollectionFactory.createConcurrentSet() + val removedRoots = ConcurrentCollectionFactory.createConcurrentSet() + + override fun workspaceFileIndexChanged(event: WorkspaceFileIndexChangedEvent) { + storedRoots.addAll(event.getStoredFileSets()) + removedRoots.addAll(event.getRemovedFileSets()) + } + } + + // we need SkipAddingToWatchedRoots to pass filter WorkspaceIndexingRootsBuilder.Companion.registerEntitiesFromContributors() + private class ParentWorkspaceFileIndexContributor : WorkspaceFileIndexContributor, SkipAddingToWatchedRoots { + + override val entityClass: Class + get() = ParentTestEntity::class.java + + override fun registerFileSets(entity: ParentTestEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) { + registrar.registerFileSet(entity.parentEntityRoot, WorkspaceFileKind.CONTENT, entity, null) + } + } +}