IJPL-197985: Add test for WorkspaceFileIndexListener

GitOrigin-RevId: 3ff0141d5be4e290dfc4f67f93f54be79450a099
This commit is contained in:
Ilya Korennoy
2025-07-22 12:28:34 +00:00
committed by intellij-monorepo-bot
parent 0d9cdd3805
commit 1976bac341
7 changed files with 172 additions and 5 deletions
@@ -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
@@ -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)
@@ -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<ConnectionId> {
@@ -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> = ParentTestEntity::class.java
}
}
@@ -211,8 +233,10 @@ internal class ParentTestEntityImpl(private val dataSource: ParentTestEntityData
@OptIn(WorkspaceEntityInternalApi::class)
internal class ParentTestEntityData : WorkspaceEntityData<ParentTestEntity>() {
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<ParentTestEntity> {
val modifiable = ParentTestEntityImpl.Builder(null)
@@ -241,7 +265,7 @@ internal class ParentTestEntityData : WorkspaceEntityData<ParentTestEntity>() {
}
override fun createDetachedEntity(parents: List<WorkspaceEntity.Builder<*>>): WorkspaceEntity.Builder<*> {
return ParentTestEntity(customParentProperty, entitySource) {
return ParentTestEntity(customParentProperty, parentEntityRoot, entitySource) {
}
}
@@ -258,6 +282,7 @@ internal class ParentTestEntityData : WorkspaceEntityData<ParentTestEntity>() {
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<ParentTestEntity>() {
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
}
}
@@ -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
@@ -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<ParentTestEntity, Builder>() {
@@ -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
@@ -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)
}
@@ -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<WorkspaceFileSet>()
val removedRoots = ConcurrentCollectionFactory.createConcurrentSet<WorkspaceFileSet>()
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<ParentTestEntity>, SkipAddingToWatchedRoots {
override val entityClass: Class<ParentTestEntity>
get() = ParentTestEntity::class.java
override fun registerFileSets(entity: ParentTestEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
registrar.registerFileSet(entity.parentEntityRoot, WorkspaceFileKind.CONTENT, entity, null)
}
}
}