From c929f363338ee85d951c1cea930629d6577768a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=92=E1=85=AD=E1=84=8B?= =?UTF-8?q?=E1=85=AE=5BSE=5D=5BSmartEditor=5D?= Date: Mon, 3 Jun 2024 09:55:09 +0900 Subject: [PATCH] Tests for concurrent tree modifications causing incorrect remote event paths --- .../dev/yorkie/document/json/JsonTreeTest.kt | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt b/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt index f589f3ecb..0b056b0b8 100644 --- a/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt +++ b/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeout import org.junit.Test @@ -2159,6 +2160,78 @@ class JsonTreeTest { } } + // Tests for concurrent tree modifications causing incorrect remote event paths. + @Test + fun test_concurrent_tree_style_and_insertion() { + withTwoClientsAndDocuments(syncMode = Client.SyncMode.Manual) { c1, c2, d1, d2, _ -> + updateAndSync( + Updater(c1, d1) { root, _ -> + root.setNewTree( + "t", + element("doc") { + element("p") + }, + ) + }, + Updater(c2, d2), + ) + assertTreesXmlEquals("""

""", d1, d2) + + val d1Events = mutableListOf() + val d2Events = mutableListOf() + + val collectJob = launch(start = CoroutineStart.UNDISPATCHED) { + launch(start = CoroutineStart.UNDISPATCHED) { + d1.events.filterIsInstance() + .map { + it.changeInfo.operations.filterIsInstance() + } + .collect(d1Events::addAll) + } + launch(start = CoroutineStart.UNDISPATCHED) { + d2.events.filterIsInstance() + .map { + it.changeInfo.operations.filterIsInstance() + } + .collect(d2Events::addAll) + } + } + + listOf( + launch { + repeat(10) { + d1.updateAsync { root, _ -> + root.rootTree().style(0, 1, mapOf("style" to it.toString())) + }.await() + } + }, + launch { + d2.updateAsync { root, _ -> + root.rootTree().edit(0, 0, element("p2")) + }.await() + }, + ).joinAll() + + c1.syncAsync().await() + c2.syncAsync().await() + c1.syncAsync().await() + + // the resulting trees are the same. + assertEquals(d1.getRoot().rootTree().toXml(), d2.getRoot().rootTree().toXml()) + + // insert event is sent with path [0] + assertEquals(listOf(0), d1Events.single().fromPath) + + // but here test fails. + // style event should be sent with path [1], but path [0] is given. + d2Events.forEach { + assertEquals(listOf(1), it.fromPath) + } + + collectJob.cancel() + } + } + companion object { fun JsonObject.rootTree() = getAs("t")