From cb82c3fc3a1248af217e3baed2b693493895ada9 Mon Sep 17 00:00:00 2001 From: jee-hyun-kim Date: Mon, 13 May 2024 21:02:26 +0900 Subject: [PATCH 1/4] Handle concurrent editing and styling in Tree --- docker/docker-compose-ci.yml | 2 +- docker/docker-compose.yml | 2 +- gradle.properties | 2 +- yorkie/proto/yorkie/v1/resources.proto | 1 + .../dev/yorkie/document/json/JsonTreeTest.kt | 62 ++++++++++++ .../dev/yorkie/api/OperationConverter.kt | 5 + .../dev/yorkie/document/crdt/CrdtText.kt | 20 ++-- .../dev/yorkie/document/crdt/CrdtTree.kt | 97 +++++++++++-------- .../dev/yorkie/document/crdt/RgaTreeSplit.kt | 30 +++--- .../kotlin/dev/yorkie/document/crdt/Rht.kt | 4 +- .../dev/yorkie/document/json/JsonTree.kt | 4 +- .../document/operation/TreeStyleOperation.kt | 9 +- .../kotlin/dev/yorkie/api/ConverterTest.kt | 1 + 13 files changed, 167 insertions(+), 72 deletions(-) diff --git a/docker/docker-compose-ci.yml b/docker/docker-compose-ci.yml index 9ca09d5f7..927d16bf9 100644 --- a/docker/docker-compose-ci.yml +++ b/docker/docker-compose-ci.yml @@ -2,7 +2,7 @@ version: '3.3' services: yorkie: - image: 'yorkieteam/yorkie:0.4.15' + image: 'yorkieteam/yorkie:0.4.19' container_name: 'yorkie' command: ['server', '--mongo-connection-uri', 'mongodb://mongo:27017'] restart: always diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4fd3b19eb..306059628 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.3' services: yorkie: - image: 'yorkieteam/yorkie:0.4.15' + image: 'yorkieteam/yorkie:0.4.19' container_name: 'yorkie' command: ['server', '--enable-pprof'] restart: always diff --git a/gradle.properties b/gradle.properties index d0ff44429..f4e8920ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,7 @@ android.suppressUnsupportedOptionWarnings=android.suppressUnsupportedOptionWarni kotlin.code.style=official kotlin.mpp.stability.nowarn=true GROUP=dev.yorkie -VERSION_NAME=0.4.16 +VERSION_NAME=0.4.19 POM_DESCRIPTION=Document store for building collaborative editing applications. POM_INCEPTION_YEAR=2022 POM_URL=https://github.com/yorkie-team/yorkie-android-sdk diff --git a/yorkie/proto/yorkie/v1/resources.proto b/yorkie/proto/yorkie/v1/resources.proto index 557833c7e..50a63b72d 100644 --- a/yorkie/proto/yorkie/v1/resources.proto +++ b/yorkie/proto/yorkie/v1/resources.proto @@ -134,6 +134,7 @@ message Operation { map attributes = 4; TimeTicket executed_at = 5; repeated string attributes_to_remove = 6; + map created_at_map_by_actor = 7; } oneof body { 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 3b90ac7fd..fc3c1ed42 100644 --- a/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt +++ b/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt @@ -20,7 +20,9 @@ import dev.yorkie.document.json.TreeBuilder.text import dev.yorkie.document.operation.OperationInfo import dev.yorkie.document.operation.OperationInfo.SetOpInfo import dev.yorkie.document.operation.OperationInfo.TreeEditOpInfo +import dev.yorkie.document.operation.OperationInfo.TreeStyleOpInfo import dev.yorkie.gson +import junit.framework.TestCase.assertTrue import kotlin.test.assertEquals import kotlin.test.assertIs import kotlinx.coroutines.CoroutineScope @@ -2099,6 +2101,66 @@ class JsonTreeTest { } } + @Test + fun test_tree_style_events_concurrency() { + withTwoClientsAndDocuments(syncMode = Manual) { c1, c2, d1, d2, _ -> + updateAndSync( + Updater(c1, d1) { root, _ -> + root.setNewTree( + "t", + element("doc") { + element("p") { + text { "hello" } + attr { "italic" to true } + } + }, + ) + }, + Updater(c2, d2), + ) + assertTreesXmlEquals("""

hello

""", 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) + } + } + + d1.updateAsync { root, _ -> + root.rootTree().style(0, 1, mapOf("bold" to "true")) + }.await() + + d2.updateAsync { root, _ -> + root.rootTree().style(0, 1, mapOf("bold" to "false")) + }.await() + + c1.syncAsync().await() + c2.syncAsync().await() + c1.syncAsync().await() + + assertEquals(d1.getRoot().rootTree().toXml(), d2.getRoot().rootTree().toXml()) + + assertEquals("false", d1Events.single().attributes["bold"]) + assertTrue(d2Events.isEmpty()) + + collectJob.cancel() + } + } + companion object { fun JsonObject.rootTree() = getAs("t") diff --git a/yorkie/src/main/kotlin/dev/yorkie/api/OperationConverter.kt b/yorkie/src/main/kotlin/dev/yorkie/api/OperationConverter.kt index 081413172..be698d7b0 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/api/OperationConverter.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/api/OperationConverter.kt @@ -109,6 +109,8 @@ internal fun List.toOperations(): List { attributes = it.treeStyle.attributesMap, executedAt = it.treeStyle.executedAt.toTimeTicket(), attributesToRemove = it.treeStyle.attributesToRemoveList, + maxCreatedAtMapByActor = it.treeStyle.createdAtMapByActorMap.entries + .associate { (key, value) -> ActorID(key) to value.toTimeTicket() }, ) else -> throw IllegalArgumentException("unimplemented operation") @@ -231,6 +233,9 @@ internal fun Operation.toPBOperation(): PBOperation { attributes[key] = value } operation.attributesToRemove?.forEach { attributesToRemove.add(it) } + operation.maxCreatedAtMapByActor?.forEach { + createdAtMapByActor[it.key.value] = it.value.toPBTimeTicket() + } } } } diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtText.kt b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtText.kt index d92ee7131..ec20a4a72 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtText.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtText.kt @@ -33,7 +33,7 @@ internal data class CrdtText( value: String, executedAt: TimeTicket, attributes: Map? = null, - latestCreatedAtMapByActor: Map? = null, + maxCreatedAtMapByActor: Map? = null, ): TextOperationResult { val textValue = if (value.isNotEmpty()) { TextValue(value).apply { @@ -43,11 +43,11 @@ internal data class CrdtText( null } - val (caretPos, latestCreatedAtMap, contentChanges) = rgaTreeSplit.edit( + val (caretPos, maxCreatedAtMap, contentChanges) = rgaTreeSplit.edit( range, executedAt, textValue, - latestCreatedAtMapByActor, + maxCreatedAtMapByActor, ) val changes = contentChanges.map { @@ -63,7 +63,7 @@ internal data class CrdtText( if (value.isNotEmpty() && attributes != null) { changes[changes.lastIndex] = changes.last().copy(attributes = attributes) } - return TextOperationResult(latestCreatedAtMap, changes, caretPos to caretPos) + return TextOperationResult(maxCreatedAtMap, changes, caretPos to caretPos) } /** @@ -75,7 +75,7 @@ internal data class CrdtText( range: RgaTreeSplitPosRange, attributes: Map, executedAt: TimeTicket, - latestCreatedAtMapByActor: Map? = null, + maxCreatedAtMapByActor: Map? = null, ): TextOperationResult { // 1. Split nodes with from and to. val toRight = rgaTreeSplit.findNodeWithSplit(range.second, executedAt).second @@ -86,18 +86,18 @@ internal data class CrdtText( val createdAtMapByActor = mutableMapOf() val toBeStyleds = nodes.mapNotNull { node -> val actorID = node.createdAt.actorID - val latestCreatedAt = if (latestCreatedAtMapByActor?.isNotEmpty() == true) { - latestCreatedAtMapByActor[actorID] ?: TimeTicket.InitialTimeTicket + val maxCreatedAt = if (maxCreatedAtMapByActor?.isNotEmpty() == true) { + maxCreatedAtMapByActor[actorID] ?: TimeTicket.InitialTimeTicket } else { TimeTicket.MaxTimeTicket } node.takeIf { - it.canStyle(executedAt, latestCreatedAt) + it.canStyle(executedAt, maxCreatedAt) }?.also { - val updatedLatestCreatedAt = createdAtMapByActor[actorID] + val updatedMaxCreatedAt = createdAtMapByActor[actorID] val updatedCreatedAt = node.createdAt - if (updatedLatestCreatedAt == null || updatedLatestCreatedAt < updatedCreatedAt) { + if (updatedMaxCreatedAt == null || updatedMaxCreatedAt < updatedCreatedAt) { createdAtMapByActor[actorID] = updatedCreatedAt } } diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtTree.kt b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtTree.kt index e400e44e8..3bc8da81b 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtTree.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/CrdtTree.kt @@ -56,34 +56,46 @@ internal data class CrdtTree( range: TreePosRange, attributes: Map?, executedAt: TimeTicket, - ): List { + maxCreatedAtMapByActor: Map? = null, + ): Pair, Map> { val (fromParent, fromLeft) = findNodesAndSplitText(range.first, executedAt) val (toParent, toLeft) = findNodesAndSplitText(range.second, executedAt) - return buildList { - traverseInPosRange( - fromParent = fromParent, - fromLeft = fromLeft, - toParent = toParent, - toLeft = toLeft, - ) { (node, _), _ -> - if (!node.isRemoved && attributes != null && !node.isText) { - attributes.forEach { (key, value) -> - node.setAttribute(key, value, executedAt) - } - add( - TreeChange( - type = TreeChangeType.Style, - from = toIndex(fromParent, fromLeft), - to = toIndex(toParent, toLeft), - fromPath = toPath(fromParent, fromLeft), - toPath = toPath(toParent, toLeft), - actorID = executedAt.actorID, - attributes = attributes, - ), - ) + val changes = mutableListOf() + val createdAtMapByActor = mutableMapOf() + + traverseInPosRange( + fromParent = fromParent, + fromLeft = fromLeft, + toParent = toParent, + toLeft = toLeft, + ) { (node, _), _ -> + val actorID = node.createdAt.actorID + val maxCreatedAt = maxCreatedAtMapByActor?.let { + maxCreatedAtMapByActor[actorID] ?: InitialTimeTicket + } ?: MaxTimeTicket + + if (node.canStyle(executedAt, maxCreatedAt) && attributes != null) { + val max = createdAtMapByActor[actorID] + val createdAt = node.createdAt + if (max == null || max < createdAt) { + createdAtMapByActor[actorID] = createdAt + } + + val affectedKeys = node.setAttributes(attributes, executedAt) + if (affectedKeys.isNotEmpty()) { + TreeChange( + type = TreeChangeType.Style, + from = toIndex(fromParent, fromLeft), + to = toIndex(toParent, toLeft), + fromPath = toPath(fromParent, fromLeft), + toPath = toPath(toParent, toLeft), + actorID = executedAt.actorID, + attributes = attributes.filterKeys { it in affectedKeys }, + ).let(changes::add) } } } + return changes to createdAtMapByActor } private fun toPath(parentNode: CrdtTreeNode, leftSiblingNode: CrdtTreeNode): List { @@ -133,7 +145,7 @@ internal data class CrdtTree( splitLevel: Int, executedAt: TimeTicket, issueTimeTicket: (() -> TimeTicket)? = null, - latestCreatedAtMapByActor: Map? = null, + maxCreatedAtMapByActor: Map? = null, ): Pair, Map> { // 01. find nodes from the given range and split nodes. val (fromParent, fromLeft) = findNodesAndSplitText(range.first, executedAt) @@ -145,7 +157,7 @@ internal data class CrdtTree( val nodesToBeRemoved = mutableListOf() val tokensToBeRemoved = mutableListOf() val toBeMovedToFromParents = mutableListOf() - val latestCreatedAtMap = mutableMapOf() + val maxCreatedAtMap = mutableMapOf() traverseInPosRange( fromParent = fromParent, @@ -160,16 +172,16 @@ internal data class CrdtTree( } val actorID = node.createdAt.actorID - val latestCreatedAt = latestCreatedAtMapByActor?.let { - latestCreatedAtMapByActor[actorID] ?: InitialTimeTicket + val maxCreatedAt = maxCreatedAtMapByActor?.let { + maxCreatedAtMapByActor[actorID] ?: InitialTimeTicket } ?: MaxTimeTicket - if (node.canDelete(executedAt, latestCreatedAt) || node.parent in nodesToBeRemoved) { - val latest = latestCreatedAtMap[actorID] + if (node.canDelete(executedAt, maxCreatedAt) || node.parent in nodesToBeRemoved) { + val max = maxCreatedAtMap[actorID] val createdAt = node.createdAt - if (latest == null || latest < createdAt) { - latestCreatedAtMap[actorID] = createdAt + if (max == null || max < createdAt) { + maxCreatedAtMap[actorID] = createdAt } if (tokenType == TokenType.Text || tokenType == TokenType.Start) { @@ -265,7 +277,7 @@ internal data class CrdtTree( } } } - return changes to latestCreatedAtMap + return changes to maxCreatedAtMap } /** @@ -806,12 +818,10 @@ internal data class CrdtTreeNode private constructor( return split } - fun setAttribute( - key: String, - value: String, - executedAt: TimeTicket, - ) { - _attributes.set(key, value, executedAt) + fun setAttributes(attributes: Map, executedAt: TimeTicket): Set { + return attributes.filter { (key, value) -> + _attributes.set(key, value, executedAt) + }.keys.toSet() } fun removeAttribute(key: String, executedAt: TimeTicket) { @@ -865,8 +875,15 @@ internal data class CrdtTreeNode private constructor( /** * Checks if node is able to delete. */ - fun canDelete(executedAt: TimeTicket, latestCreatedAt: TimeTicket): Boolean { - return createdAt <= latestCreatedAt && (removedAt == null || removedAt < executedAt) + fun canDelete(executedAt: TimeTicket, maxCreatedAt: TimeTicket): Boolean { + return createdAt <= maxCreatedAt && (removedAt == null || removedAt < executedAt) + } + + fun canStyle(executedAt: TimeTicket, maxCreatedAt: TimeTicket): Boolean { + if (isText) { + return false + } + return createdAt <= maxCreatedAt && (removedAt == null || removedAt < executedAt) } @Suppress("FunctionName") diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/RgaTreeSplit.kt b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/RgaTreeSplit.kt index 708577a8a..043bd3cb7 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/RgaTreeSplit.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/RgaTreeSplit.kt @@ -47,7 +47,7 @@ internal class RgaTreeSplit> : Iterable? = null, + maxCreatedAtMapByActor: Map? = null, ): Triple, List> { // 1. Split nodes. val (toLeft, toRight) = findNodeWithSplit(range.second, executedAt) @@ -55,10 +55,10 @@ internal class RgaTreeSplit> : Iterable> : Iterable> : Iterable>, executedAt: TimeTicket, - latestCreatedAtMapByActor: Map?, + maxCreatedAtMapByActor: Map?, ): Triple< MutableList, Map, @@ -203,7 +203,7 @@ internal class RgaTreeSplit> : Iterable() val removedNodeMap = mutableMapOf>() @@ -230,9 +230,9 @@ internal class RgaTreeSplit> : Iterable>, executedAt: TimeTicket, - latestCreatedAtMapByActor: Map?, + maxCreatedAtMapByActor: Map?, ): Pair>, List?>> { - val isRemote = latestCreatedAtMapByActor != null + val isRemote = maxCreatedAtMapByActor != null val nodesToDelete = mutableListOf>() val nodesToKeep = mutableListOf?>() @@ -241,12 +241,12 @@ internal class RgaTreeSplit> : Iterable val actorID = node.createdAt.actorID - val latestCreatedAt = if (isRemote) { - latestCreatedAtMapByActor?.get(actorID) ?: InitialTimeTicket + val maxCreatedAt = if (isRemote) { + maxCreatedAtMapByActor?.get(actorID) ?: InitialTimeTicket } else { MaxTimeTicket } - if (node.canDelete(executedAt, latestCreatedAt)) { + if (node.canDelete(executedAt, maxCreatedAt)) { nodesToDelete.add(node) } else { nodesToKeep.add(node) @@ -529,15 +529,15 @@ internal data class RgaTreeSplitNode>( /** * Checks if this [RgaTreeSplitNode] can be deleted or not. */ - fun canDelete(executedAt: TimeTicket, latestCreatedAt: TimeTicket): Boolean { - return createdAt <= latestCreatedAt && (isRemoved || _removedAt < executedAt) + fun canDelete(executedAt: TimeTicket, maxCreatedAt: TimeTicket): Boolean { + return createdAt <= maxCreatedAt && (isRemoved || _removedAt < executedAt) } /** * Checks if node is able to set style. */ - fun canStyle(executedAt: TimeTicket, latestCreatedAt: TimeTicket): Boolean { - return createdAt <= latestCreatedAt && removedAt < executedAt + fun canStyle(executedAt: TimeTicket, maxCreatedAt: TimeTicket): Boolean { + return createdAt <= maxCreatedAt && removedAt < executedAt } /** diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/Rht.kt b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/Rht.kt index ab02c9fde..b07bb7da3 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/crdt/Rht.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/crdt/Rht.kt @@ -24,7 +24,7 @@ internal class Rht : Collection { value: String, executedAt: TimeTicket, isRemoved: Boolean = false, - ) { + ): Boolean { val prev = nodeMapByKey[key] if (prev?.executedAt < executedAt) { if (prev?.isRemoved == false) { @@ -32,7 +32,9 @@ internal class Rht : Collection { } val node = Node(key, value, executedAt, isRemoved) nodeMapByKey[key] = node + return true } + return false } /** diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonTree.kt b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonTree.kt index acda77822..52447a798 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonTree.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/json/JsonTree.kt @@ -9,7 +9,6 @@ import dev.yorkie.document.crdt.CrdtTreeNodeID import dev.yorkie.document.crdt.CrdtTreePos import dev.yorkie.document.crdt.Rht import dev.yorkie.document.crdt.TreeElementNode -import dev.yorkie.document.crdt.TreeNode import dev.yorkie.document.crdt.TreePosRange import dev.yorkie.document.crdt.TreeTextNode import dev.yorkie.document.operation.TreeEditOperation @@ -66,7 +65,7 @@ public class JsonTree internal constructor( private fun styleByRange(range: TreePosRange, attributes: Map) { val ticket = context.issueTimeTicket() - target.style(range, attributes, ticket) + val (_, maxCreatedAtMapByActor) = target.style(range, attributes, ticket) context.push( TreeStyleOperation( @@ -74,6 +73,7 @@ public class JsonTree internal constructor( range.first, range.second, ticket, + maxCreatedAtMapByActor, attributes.toMap(), ), ) diff --git a/yorkie/src/main/kotlin/dev/yorkie/document/operation/TreeStyleOperation.kt b/yorkie/src/main/kotlin/dev/yorkie/document/operation/TreeStyleOperation.kt index 9682bc830..569d40494 100644 --- a/yorkie/src/main/kotlin/dev/yorkie/document/operation/TreeStyleOperation.kt +++ b/yorkie/src/main/kotlin/dev/yorkie/document/operation/TreeStyleOperation.kt @@ -4,6 +4,7 @@ import dev.yorkie.document.crdt.CrdtRoot import dev.yorkie.document.crdt.CrdtTree import dev.yorkie.document.crdt.CrdtTreePos import dev.yorkie.document.operation.OperationInfo.TreeStyleOpInfo +import dev.yorkie.document.time.ActorID import dev.yorkie.document.time.TimeTicket import dev.yorkie.util.YorkieLogger @@ -15,6 +16,7 @@ internal data class TreeStyleOperation( val fromPos: CrdtTreePos, val toPos: CrdtTreePos, override var executedAt: TimeTicket, + val maxCreatedAtMapByActor: Map? = null, val attributes: Map? = null, val attributesToRemove: List? = null, ) : Operation() { @@ -33,7 +35,12 @@ internal data class TreeStyleOperation( return when { attributes?.isNotEmpty() == true -> { - tree.style(fromPos to toPos, attributes, executedAt).map { + tree.style( + fromPos to toPos, + attributes, + executedAt, + maxCreatedAtMapByActor, + ).first.map { TreeStyleOpInfo( it.from, it.to, diff --git a/yorkie/src/test/kotlin/dev/yorkie/api/ConverterTest.kt b/yorkie/src/test/kotlin/dev/yorkie/api/ConverterTest.kt index 72806f330..643ee1970 100644 --- a/yorkie/src/test/kotlin/dev/yorkie/api/ConverterTest.kt +++ b/yorkie/src/test/kotlin/dev/yorkie/api/ConverterTest.kt @@ -262,6 +262,7 @@ class ConverterTest { CrdtTreeNodeID(InitialTimeTicket, 10), ), InitialTimeTicket, + mapOf(INITIAL_ACTOR_ID to InitialTimeTicket), mapOf("a" to "b"), listOf("a"), ) From 3fbd38c360b3084d58655e45ed981d9f6129d8ec Mon Sep 17 00:00:00 2001 From: jee-hyun-kim Date: Tue, 14 May 2024 10:33:59 +0900 Subject: [PATCH 2/4] use kotlin.test.assertTrue --- .../androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fc3c1ed42..9fceda2af 100644 --- a/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt +++ b/yorkie/src/androidTest/kotlin/dev/yorkie/document/json/JsonTreeTest.kt @@ -22,9 +22,9 @@ import dev.yorkie.document.operation.OperationInfo.SetOpInfo import dev.yorkie.document.operation.OperationInfo.TreeEditOpInfo import dev.yorkie.document.operation.OperationInfo.TreeStyleOpInfo import dev.yorkie.gson -import junit.framework.TestCase.assertTrue import kotlin.test.assertEquals import kotlin.test.assertIs +import kotlin.test.assertTrue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.ExperimentalCoroutinesApi From b252bd7cc861a116dba68140201804b478afb0c4 Mon Sep 17 00:00:00 2001 From: jee-hyun-kim Date: Tue, 14 May 2024 11:49:01 +0900 Subject: [PATCH 3/4] update ci configuration --- .github/workflows/ci.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d457f1ce..73b671bb1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,10 +116,9 @@ jobs: - uses: actions/download-artifact@v2 - uses: codecov/codecov-action@v3 with: + token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true verbose: false - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} microbenchmarks: name: Microbenchmarks @@ -144,7 +143,7 @@ jobs: - if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: - api-level: 30 + api-level: 28 target: google_apis arch: x86_64 force-avd-creation: false @@ -154,7 +153,7 @@ jobs: script: echo "Generated AVD snapshot for caching." - uses: reactivecircus/android-emulator-runner@v2 with: - api-level: 30 + api-level: 28 target: google_apis arch: x86_64 force-avd-creation: false From 3738b1e5b66048c24a2362e5a870a5c9db0ee071 Mon Sep 17 00:00:00 2001 From: jee-hyun-kim Date: Tue, 14 May 2024 13:01:39 +0900 Subject: [PATCH 4/4] api 30 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 73b671bb1..9b7f9b3c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,7 +143,7 @@ jobs: - if: steps.avd-cache.outputs.cache-hit != 'true' uses: reactivecircus/android-emulator-runner@v2 with: - api-level: 28 + api-level: 30 target: google_apis arch: x86_64 force-avd-creation: false @@ -153,7 +153,7 @@ jobs: script: echo "Generated AVD snapshot for caching." - uses: reactivecircus/android-emulator-runner@v2 with: - api-level: 28 + api-level: 30 target: google_apis arch: x86_64 force-avd-creation: false