From 9dbd450fdfceeac49b2b90a50ae7e863280fc6eb Mon Sep 17 00:00:00 2001 From: raararaara Date: Fri, 8 Mar 2024 10:45:31 +0900 Subject: [PATCH 01/14] Add test code --- test/integration/main_test.go | 9 ++ test/integration/tree_test.go | 226 ++++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+) diff --git a/test/integration/main_test.go b/test/integration/main_test.go index e63be3137..484a0c769 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -119,6 +119,15 @@ func syncClientsThenCheckEqual(t *testing.T, pairs []clientAndDocPair) bool { return true } +// activateSingleClient creates and activates a single client. +func activateSingleClient(t *testing.T) *client.Client { + c, err := client.Dial(defaultServer.RPCAddr()) + assert.NoError(t, err) + assert.NoError(t, c.Activate(context.Background())) + + return c +} + // activeClients creates and activates the given number of clients. func activeClients(t *testing.T, n int) (clients []*client.Client) { for i := 0; i < n; i++ { diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go index b9726058b..280764c8c 100644 --- a/test/integration/tree_test.go +++ b/test/integration/tree_test.go @@ -792,6 +792,232 @@ func TestTree(t *testing.T) { assert.Equal(t, `{"type":"root","children":[{"type":"p","children":[{"type":"text","value":"ab"}]},{"type":"p","children":[{"type":"text","value":"cd"}]}]}`, doc.Root().GetTree("t").Marshal()) }) + // Concurrent editing, client reload, after edit test + t.Run("concurrently edit with client reload case test", func(t *testing.T) { + ctx := context.Background() + d1 := document.New(helper.TestDocKey(t)) + assert.NoError(t, c1.Attach(ctx, d1)) + d2 := document.New(helper.TestDocKey(t)) + assert.NoError(t, c2.Attach(ctx, d2)) + + const snapshotThreshold = 500 + //assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + // for idx := 0; idx < snapshotThreshold; idx++ { + // root.SetInteger("i", 0) + // } + // return nil + //})) + + for idx := 0; idx < snapshotThreshold; idx++ { + assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + root.SetInteger("i", 0) + + return nil + })) + } + + assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + root.SetNewTree("t", &json.TreeNode{ + Type: "r", + Children: []json.TreeNode{{ + Type: "c", + Children: []json.TreeNode{{ + Type: "u", Children: []json.TreeNode{{ + Type: "p", + Children: []json.TreeNode{{ + Type: "n", + Children: []json.TreeNode{}, + }}, + }}, + }}, + }, + { + Type: "c", + Children: []json.TreeNode{{ + Type: "p", + Children: []json.TreeNode{{ + Type: "n", + Children: []json.TreeNode{}, + }}, + }}, + }}, + }) + return nil + })) + assert.NoError(t, c1.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 0}, []int{1, 0, 0, 0}, &json.TreeNode{ + Type: "text", + Value: "1", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 1}, &json.TreeNode{ + Type: "text", + Value: "2", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ + Type: "text", + Value: "3", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ + Type: "text", + Value: " ", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 3}, &json.TreeNode{ + Type: "text", + Value: "네이버랑 ", + }, 0) + return nil + })) + + assert.NoError(t, c1.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

12 네이버랑 3

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

12 네이버랑 3

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 8}, &json.TreeNode{ + Type: "text", + Value: " 2 네이버랑 ", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ + Type: "text", + Value: "ㅋ", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, &json.TreeNode{ + Type: "text", + Value: "카", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, &json.TreeNode{ + Type: "text", + Value: "캌", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, &json.TreeNode{ + Type: "text", + Value: "카카", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 4}, &json.TreeNode{ + Type: "text", + Value: "캉", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 4}, &json.TreeNode{ + Type: "text", + Value: "카오", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 4}, []int{1, 0, 0, 5}, &json.TreeNode{ + Type: "text", + Value: "올", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 4}, []int{1, 0, 0, 5}, &json.TreeNode{ + Type: "text", + Value: "오라", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 5}, []int{1, 0, 0, 6}, &json.TreeNode{ + Type: "text", + Value: "랑", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 6}, &json.TreeNode{ + Type: "text", + Value: " ", + }, 0) + + return nil + })) + + assert.NoError(t, c2.Sync(ctx)) + assert.NoError(t, c1.Sync(ctx)) + assert.Equal(t, "

1 카카오랑 2 네이버랑 3

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1 카카오랑 2 네이버랑 3

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 13}, []int{1, 0, 0, 14}, nil, 0) + root.GetTree("t").EditByPath([]int{1, 0, 0, 12}, []int{1, 0, 0, 13}, nil, 0) + + return nil + })) + + assert.NoError(t, c1.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

1 카카오랑 2 네이버3

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1 카카오랑 2 네이버3

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 7}, nil, 0) + root.GetTree("t").EditByPath([]int{1, 0, 0, 5}, []int{1, 0, 0, 6}, nil, 0) + + return nil + })) + + assert.NoError(t, c2.Sync(ctx)) + assert.NoError(t, c1.Sync(ctx)) + assert.Equal(t, "

1 카카오2 네이버3

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1 카카오2 네이버3

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 9}, []int{1, 0, 0, 10}, nil, 0) + + return nil + })) + + assert.NoError(t, c1.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

1 카카오2 네이3

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1 카카오2 네이3

", d2.Root().GetTree("t").ToXML()) + + c3 := activateSingleClient(t) + d3 := document.New(helper.TestDocKey(t)) + assert.NoError(t, c3.Attach(ctx, d3)) + + assert.Equal(t, "

1 카카오2 네이3

", d3.Root().GetTree("t").ToXML()) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

1 카카오2 네이3

", d3.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1 카카오2 네이3

", d2.Root().GetTree("t").ToXML()) + + // TODO: panic: unreachable path + assert.NoError(t, d3.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 4}, []int{1, 0, 0, 5}, nil, 0) + root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 4}, nil, 0) + + return nil + })) + + assert.NoError(t, c3.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

1 카2 네이3

", d3.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1 카2 네이3

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d3.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, nil, 0) + + return nil + })) + + assert.NoError(t, c3.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

1 2 네이3

", d3.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1 2 네이3

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, c3.Deactivate(context.Background())) + assert.NoError(t, c3.Close()) + }) + // Concurrent editing, overlapping range test t.Run("concurrently delete overlapping elements test", func(t *testing.T) { ctx := context.Background() From a7c759aead201ccdda0bc82504b5da0cc4c142be Mon Sep 17 00:00:00 2001 From: raararaara Date: Mon, 11 Mar 2024 11:06:48 +0900 Subject: [PATCH 02/14] Add missing `insPrevID` and `insNextID` to deepcopy --- pkg/document/crdt/tree.go | 2 ++ pkg/index/tree.go | 4 +++- test/integration/tree_test.go | 6 ------ 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pkg/document/crdt/tree.go b/pkg/document/crdt/tree.go index ad160a420..9704c6b2a 100644 --- a/pkg/document/crdt/tree.go +++ b/pkg/document/crdt/tree.go @@ -361,6 +361,8 @@ func (n *TreeNode) DeepCopy() (*TreeNode, error) { } clone.RemovedAt = n.RemovedAt + clone.InsPrevID = n.InsPrevID + clone.InsNextID = n.InsNextID if n.IsText() { return clone, nil } diff --git a/pkg/index/tree.go b/pkg/index/tree.go index 703c7493c..076f6187e 100644 --- a/pkg/index/tree.go +++ b/pkg/index/tree.go @@ -319,7 +319,9 @@ func (n *Node[V]) SetChildren(children []*Node[V]) error { n.children = children for _, child := range children { child.Parent = n - child.UpdateAncestorsSize() + if !child.Value.IsRemoved() { + child.UpdateAncestorsSize() + } } return nil diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go index 280764c8c..72b47f381 100644 --- a/test/integration/tree_test.go +++ b/test/integration/tree_test.go @@ -801,12 +801,6 @@ func TestTree(t *testing.T) { assert.NoError(t, c2.Attach(ctx, d2)) const snapshotThreshold = 500 - //assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - // for idx := 0; idx < snapshotThreshold; idx++ { - // root.SetInteger("i", 0) - // } - // return nil - //})) for idx := 0; idx < snapshotThreshold; idx++ { assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { From 48ac6ec799caedcbc765679e0884db4cf83842dd Mon Sep 17 00:00:00 2001 From: raararaara Date: Mon, 11 Mar 2024 18:22:09 +0900 Subject: [PATCH 03/14] Add test for incorrect calculation in `indexTree.treePosToPath` operation --- pkg/document/crdt/tree.go | 13 +++ pkg/document/internal_document.go | 5 ++ pkg/index/tree.go | 23 +++++- test/integration/tree_test.go | 128 ++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+), 3 deletions(-) diff --git a/pkg/document/crdt/tree.go b/pkg/document/crdt/tree.go index 9704c6b2a..89230bda0 100644 --- a/pkg/document/crdt/tree.go +++ b/pkg/document/crdt/tree.go @@ -1032,6 +1032,19 @@ func (t *Tree) ToIndex(parentNode, leftSiblingNode *TreeNode) (int, error) { return idx, nil } +// ToPath returns path from given CRDTTreePos +func (t *Tree) ToPath(parentNode, leftSiblingNode *TreeNode) ([]int, error) { + treePos, err := t.toTreePos(parentNode, leftSiblingNode) + if err != nil { + return make([]int, 0), err + } + if treePos == nil { + return make([]int, 0), nil + } + + return t.IndexTree.TreePosToPath(treePos) +} + // findFloorNode returns node from given id. func (t *Tree) findFloorNode(id *TreeNodeID) *TreeNode { key, node := t.NodeMapByID.Floor(id) diff --git a/pkg/document/internal_document.go b/pkg/document/internal_document.go index 4f1cdd1ad..73eea7d24 100644 --- a/pkg/document/internal_document.go +++ b/pkg/document/internal_document.go @@ -372,3 +372,8 @@ func (d *InternalDocument) AddOnlineClient(clientID string) { func (d *InternalDocument) RemoveOnlineClient(clientID string) { d.onlineClients.Delete(clientID) } + +// GetLastLocalChange returns the latest local change info for debugging. +func (d *InternalDocument) GetLastLocalChange() *change.Change { + return d.localChanges[len(d.localChanges)-1] +} diff --git a/pkg/index/tree.go b/pkg/index/tree.go index 076f6187e..ae66fbec8 100644 --- a/pkg/index/tree.go +++ b/pkg/index/tree.go @@ -597,10 +597,19 @@ func (n *Node[V]) InsertAfter(newNode, referenceNode *Node[V]) error { // HasTextChild returns true if the node has a text child. func (n *Node[V]) HasTextChild() bool { - for _, child := range n.Children() { - if child.IsText() { - return true + //for _, child := range n.Children() { + // if child.IsText() { + // return true + // } + //} + + if len(n.children) > 0 { + for _, child := range n.Children() { + if !child.IsText() { + return false + } } + return true } return false @@ -730,7 +739,15 @@ func (t *Tree[V]) TreePosToPath(treePos *TreePos[V]) ([]int, error) { node = node.Parent path = append(path, leftSiblingsSize+treePos.Offset) + } else if node.HasTextChild() { + sizeOfLeftSiblings, err := t.LeftSiblingsSize(node, treePos.Offset) + if err != nil { + return nil, err + } + + path = append(path, sizeOfLeftSiblings) } else { + //} else { path = append(path, treePos.Offset) } diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go index 72b47f381..705a45791 100644 --- a/test/integration/tree_test.go +++ b/test/integration/tree_test.go @@ -20,6 +20,7 @@ package integration import ( "context" + "github.com/yorkie-team/yorkie/pkg/document/operations" "testing" "github.com/stretchr/testify/assert" @@ -792,6 +793,133 @@ func TestTree(t *testing.T) { assert.Equal(t, `{"type":"root","children":[{"type":"p","children":[{"type":"text","value":"ab"}]},{"type":"p","children":[{"type":"text","value":"cd"}]}]}`, doc.Root().GetTree("t").Marshal()) }) + t.Run("auxiliary test", func(t *testing.T) { + ctx := context.Background() + d1 := document.New(helper.TestDocKey(t)) + assert.NoError(t, c1.Attach(ctx, d1)) + d2 := document.New(helper.TestDocKey(t)) + assert.NoError(t, c2.Attach(ctx, d2)) + + assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + root.SetNewTree("t", &json.TreeNode{ + Type: "r", + Children: []json.TreeNode{{ + Type: "c", + Children: []json.TreeNode{{ + Type: "u", Children: []json.TreeNode{{ + Type: "p", + Children: []json.TreeNode{{ + Type: "n", + Children: []json.TreeNode{}, + }}, + }}, + }}, + }, + { + Type: "c", + Children: []json.TreeNode{{ + Type: "p", + Children: []json.TreeNode{{ + Type: "n", + Children: []json.TreeNode{}, + }}, + }}, + }}, + }) + return nil + })) + assert.NoError(t, c1.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 0}, []int{1, 0, 0, 0}, &json.TreeNode{ + Type: "text", + Value: "1", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 1}, &json.TreeNode{ + Type: "text", + Value: "2", + }, 0) + + root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ + Type: "text", + Value: "3", + }, 0) + + return nil + })) + + assert.NoError(t, c2.Sync(ctx)) + assert.NoError(t, c1.Sync(ctx)) + assert.Equal(t, "

123

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

123

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 1}, &json.TreeNode{ + Type: "text", + Value: "abcdefgh", + }, 0) + + return nil + })) + + assert.NoError(t, c1.Sync(ctx)) + assert.NoError(t, c2.Sync(ctx)) + assert.Equal(t, "

1abcdefgh23

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1abcdefgh23

", d2.Root().GetTree("t").ToXML()) + + assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 5}, []int{1, 0, 0, 5}, &json.TreeNode{ + Type: "text", + Value: "4", + }, 0) + root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 7}, nil, 0) + root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 6}, &json.TreeNode{ + Type: "text", + Value: "5", + }, 0) + + return nil + })) + + assert.NoError(t, c2.Sync(ctx)) + assert.NoError(t, c1.Sync(ctx)) + + assert.Equal(t, "

1abcd45fgh23

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1abcd45fgh23

", d2.Root().GetTree("t").ToXML()) + + // TODO: need to generate & check path comparison like js-sdk + assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { + root.GetTree("t").EditByPath([]int{1, 0, 0, 7}, []int{1, 0, 0, 8}, nil, 0) + + return nil + })) + + lastChangeOps := d2.InternalDocument().GetLastLocalChange().Operations() + if len(lastChangeOps) > 0 { + if treeEdit, ok := lastChangeOps[0].(*operations.TreeEdit); ok { + fromPos := treeEdit.FromPos() + fromParent, fromLeft, err := d2.Root().GetTree("t").FindTreeNodesWithSplitText(fromPos, lastChangeOps[0].ExecutedAt()) + + if err == nil { + fromPath, err2 := d2.Root().GetTree("t").ToPath(fromParent, fromLeft) + if err2 == nil { + assert.Equal(t, fromPath, []int{1, 0, 0, 4}) + } + } + } + } + + assert.NoError(t, c2.Sync(ctx)) + assert.NoError(t, c1.Sync(ctx)) + + assert.Equal(t, "

1abcd45gh23

", d1.Root().GetTree("t").ToXML()) + assert.Equal(t, "

1abcd45gh23

", d2.Root().GetTree("t").ToXML()) + }) + // Concurrent editing, client reload, after edit test t.Run("concurrently edit with client reload case test", func(t *testing.T) { ctx := context.Background() From 566957e28839c95a82b72cae6f1557614c290dc8 Mon Sep 17 00:00:00 2001 From: raararaara Date: Tue, 12 Mar 2024 10:40:31 +0900 Subject: [PATCH 04/14] Revert "Add test for incorrect calculation in `indexTree.treePosToPath` operation" This reverts commit 48ac6ec799caedcbc765679e0884db4cf83842dd. --- pkg/document/crdt/tree.go | 13 --- pkg/document/internal_document.go | 5 -- pkg/index/tree.go | 23 +----- test/integration/tree_test.go | 128 ------------------------------ 4 files changed, 3 insertions(+), 166 deletions(-) diff --git a/pkg/document/crdt/tree.go b/pkg/document/crdt/tree.go index 89230bda0..9704c6b2a 100644 --- a/pkg/document/crdt/tree.go +++ b/pkg/document/crdt/tree.go @@ -1032,19 +1032,6 @@ func (t *Tree) ToIndex(parentNode, leftSiblingNode *TreeNode) (int, error) { return idx, nil } -// ToPath returns path from given CRDTTreePos -func (t *Tree) ToPath(parentNode, leftSiblingNode *TreeNode) ([]int, error) { - treePos, err := t.toTreePos(parentNode, leftSiblingNode) - if err != nil { - return make([]int, 0), err - } - if treePos == nil { - return make([]int, 0), nil - } - - return t.IndexTree.TreePosToPath(treePos) -} - // findFloorNode returns node from given id. func (t *Tree) findFloorNode(id *TreeNodeID) *TreeNode { key, node := t.NodeMapByID.Floor(id) diff --git a/pkg/document/internal_document.go b/pkg/document/internal_document.go index 73eea7d24..4f1cdd1ad 100644 --- a/pkg/document/internal_document.go +++ b/pkg/document/internal_document.go @@ -372,8 +372,3 @@ func (d *InternalDocument) AddOnlineClient(clientID string) { func (d *InternalDocument) RemoveOnlineClient(clientID string) { d.onlineClients.Delete(clientID) } - -// GetLastLocalChange returns the latest local change info for debugging. -func (d *InternalDocument) GetLastLocalChange() *change.Change { - return d.localChanges[len(d.localChanges)-1] -} diff --git a/pkg/index/tree.go b/pkg/index/tree.go index ae66fbec8..076f6187e 100644 --- a/pkg/index/tree.go +++ b/pkg/index/tree.go @@ -597,19 +597,10 @@ func (n *Node[V]) InsertAfter(newNode, referenceNode *Node[V]) error { // HasTextChild returns true if the node has a text child. func (n *Node[V]) HasTextChild() bool { - //for _, child := range n.Children() { - // if child.IsText() { - // return true - // } - //} - - if len(n.children) > 0 { - for _, child := range n.Children() { - if !child.IsText() { - return false - } + for _, child := range n.Children() { + if child.IsText() { + return true } - return true } return false @@ -739,15 +730,7 @@ func (t *Tree[V]) TreePosToPath(treePos *TreePos[V]) ([]int, error) { node = node.Parent path = append(path, leftSiblingsSize+treePos.Offset) - } else if node.HasTextChild() { - sizeOfLeftSiblings, err := t.LeftSiblingsSize(node, treePos.Offset) - if err != nil { - return nil, err - } - - path = append(path, sizeOfLeftSiblings) } else { - //} else { path = append(path, treePos.Offset) } diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go index 705a45791..72b47f381 100644 --- a/test/integration/tree_test.go +++ b/test/integration/tree_test.go @@ -20,7 +20,6 @@ package integration import ( "context" - "github.com/yorkie-team/yorkie/pkg/document/operations" "testing" "github.com/stretchr/testify/assert" @@ -793,133 +792,6 @@ func TestTree(t *testing.T) { assert.Equal(t, `{"type":"root","children":[{"type":"p","children":[{"type":"text","value":"ab"}]},{"type":"p","children":[{"type":"text","value":"cd"}]}]}`, doc.Root().GetTree("t").Marshal()) }) - t.Run("auxiliary test", func(t *testing.T) { - ctx := context.Background() - d1 := document.New(helper.TestDocKey(t)) - assert.NoError(t, c1.Attach(ctx, d1)) - d2 := document.New(helper.TestDocKey(t)) - assert.NoError(t, c2.Attach(ctx, d2)) - - assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - root.SetNewTree("t", &json.TreeNode{ - Type: "r", - Children: []json.TreeNode{{ - Type: "c", - Children: []json.TreeNode{{ - Type: "u", Children: []json.TreeNode{{ - Type: "p", - Children: []json.TreeNode{{ - Type: "n", - Children: []json.TreeNode{}, - }}, - }}, - }}, - }, - { - Type: "c", - Children: []json.TreeNode{{ - Type: "p", - Children: []json.TreeNode{{ - Type: "n", - Children: []json.TreeNode{}, - }}, - }}, - }}, - }) - return nil - })) - assert.NoError(t, c1.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 0}, []int{1, 0, 0, 0}, &json.TreeNode{ - Type: "text", - Value: "1", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 1}, &json.TreeNode{ - Type: "text", - Value: "2", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ - Type: "text", - Value: "3", - }, 0) - - return nil - })) - - assert.NoError(t, c2.Sync(ctx)) - assert.NoError(t, c1.Sync(ctx)) - assert.Equal(t, "

123

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

123

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 1}, &json.TreeNode{ - Type: "text", - Value: "abcdefgh", - }, 0) - - return nil - })) - - assert.NoError(t, c1.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

1abcdefgh23

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1abcdefgh23

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 5}, []int{1, 0, 0, 5}, &json.TreeNode{ - Type: "text", - Value: "4", - }, 0) - root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 7}, nil, 0) - root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 6}, &json.TreeNode{ - Type: "text", - Value: "5", - }, 0) - - return nil - })) - - assert.NoError(t, c2.Sync(ctx)) - assert.NoError(t, c1.Sync(ctx)) - - assert.Equal(t, "

1abcd45fgh23

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1abcd45fgh23

", d2.Root().GetTree("t").ToXML()) - - // TODO: need to generate & check path comparison like js-sdk - assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 7}, []int{1, 0, 0, 8}, nil, 0) - - return nil - })) - - lastChangeOps := d2.InternalDocument().GetLastLocalChange().Operations() - if len(lastChangeOps) > 0 { - if treeEdit, ok := lastChangeOps[0].(*operations.TreeEdit); ok { - fromPos := treeEdit.FromPos() - fromParent, fromLeft, err := d2.Root().GetTree("t").FindTreeNodesWithSplitText(fromPos, lastChangeOps[0].ExecutedAt()) - - if err == nil { - fromPath, err2 := d2.Root().GetTree("t").ToPath(fromParent, fromLeft) - if err2 == nil { - assert.Equal(t, fromPath, []int{1, 0, 0, 4}) - } - } - } - } - - assert.NoError(t, c2.Sync(ctx)) - assert.NoError(t, c1.Sync(ctx)) - - assert.Equal(t, "

1abcd45gh23

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1abcd45gh23

", d2.Root().GetTree("t").ToXML()) - }) - // Concurrent editing, client reload, after edit test t.Run("concurrently edit with client reload case test", func(t *testing.T) { ctx := context.Background() From 18fb4641e445c4c9622cd7a4a5d81f99a6625c27 Mon Sep 17 00:00:00 2001 From: raararaara Date: Mon, 18 Mar 2024 10:34:18 +0900 Subject: [PATCH 05/14] Erase unnecessary comment at test code --- test/integration/tree_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go index 72b47f381..5bd4f151e 100644 --- a/test/integration/tree_test.go +++ b/test/integration/tree_test.go @@ -984,7 +984,6 @@ func TestTree(t *testing.T) { assert.Equal(t, "

1 카카오2 네이3

", d3.Root().GetTree("t").ToXML()) assert.Equal(t, "

1 카카오2 네이3

", d2.Root().GetTree("t").ToXML()) - // TODO: panic: unreachable path assert.NoError(t, d3.Update(func(root *json.Object, p *presence.Presence) error { root.GetTree("t").EditByPath([]int{1, 0, 0, 4}, []int{1, 0, 0, 5}, nil, 0) root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 4}, nil, 0) From 69d12b09c03a36524e4f6de63195bc340167fa3c Mon Sep 17 00:00:00 2001 From: raararaara Date: Wed, 20 Mar 2024 17:32:37 +0900 Subject: [PATCH 06/14] Modified deepcopy to update length --- pkg/document/crdt/tree.go | 5 +++-- pkg/index/tree.go | 19 +------------------ 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/pkg/document/crdt/tree.go b/pkg/document/crdt/tree.go index 9704c6b2a..f2a63225c 100644 --- a/pkg/document/crdt/tree.go +++ b/pkg/document/crdt/tree.go @@ -298,10 +298,10 @@ func (n *TreeNode) SplitElement(offset int, issueTimeTicket func() *time.Ticket) leftChildren := n.Index.Children(true)[0:offset] rightChildren := n.Index.Children(true)[offset:] - if err := n.Index.SetChildrenInternal(leftChildren); err != nil { + if err := n.Index.SetChildren(leftChildren); err != nil { return nil, err } - if err := split.Index.SetChildrenInternal(rightChildren); err != nil { + if err := split.Index.SetChildren(rightChildren); err != nil { return nil, err } @@ -360,6 +360,7 @@ func (n *TreeNode) DeepCopy() (*TreeNode, error) { clone = NewTreeNode(n.ID, n.Type(), nil, n.Value) } clone.RemovedAt = n.RemovedAt + clone.Index.Length = n.Index.Length clone.InsPrevID = n.InsPrevID clone.InsNextID = n.InsNextID diff --git a/pkg/index/tree.go b/pkg/index/tree.go index 076f6187e..428110360 100644 --- a/pkg/index/tree.go +++ b/pkg/index/tree.go @@ -311,25 +311,8 @@ func (n *Node[V]) Children(includeRemovedNode ...bool) []*Node[V] { } // SetChildren sets the children of the given node. -func (n *Node[V]) SetChildren(children []*Node[V]) error { - if n.IsText() { - return ErrInvalidMethodCallForTextNode - } - - n.children = children - for _, child := range children { - child.Parent = n - if !child.Value.IsRemoved() { - child.UpdateAncestorsSize() - } - } - - return nil -} - -// SetChildrenInternal sets the children of the given node. // This method does not update the size of the ancestors. -func (n *Node[V]) SetChildrenInternal(children []*Node[V]) error { +func (n *Node[V]) SetChildren(children []*Node[V]) error { if n.IsText() { return ErrInvalidMethodCallForTextNode } From 891840b9ce7dbb83268d0950f4682f99e26320a7 Mon Sep 17 00:00:00 2001 From: raararaara Date: Wed, 20 Mar 2024 19:46:16 +0900 Subject: [PATCH 07/14] Add test code related to `treeNode.Length` patch --- pkg/document/crdt/tree_test.go | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index 05f0f5645..b03feca2c 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -416,6 +416,42 @@ func TestTreeEdit(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 0, idx) }) + + t.Run("length test", func(t *testing.T) { + root := helper.TestRoot() + ctx := helper.TextChangeContext(root) + + // 01. Create a tree and insert a paragraph with text. + tree := crdt.NewTree(crdt.NewTreeNode(helper.PosT(ctx), "r", nil), helper.TimeT(ctx)) + assert.Equal(t, 0, tree.Root().Len()) + assert.Equal(t, "", tree.ToXML()) + + _, err := tree.EditT(0, 0, []*crdt.TreeNode{crdt.NewTreeNode(helper. + PosT(ctx), "p", nil)}, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

", tree.ToXML()) + assert.Equal(t, 2, tree.Root().Len()) + + _, err = tree.EditT(1, 1, []*crdt.TreeNode{ + crdt.NewTreeNode(helper.PosT(ctx), "text", nil, "hel_lo"), + }, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

hel_lo

", tree.ToXML()) + assert.Equal(t, 8, tree.Root().Len()) + + // 02. Erase 3rd character from the text. + _, err = tree.EditT(4, 5, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

hello

", tree.ToXML()) + assert.Equal(t, 7, tree.Root().Len()) + + // 03. Makes a deep copy of root. + copyRoot, err := tree.Root().DeepCopy() + assert.NoError(t, err) + + // 04. Check if the length of the deep copied root is the same as the length of the original. + assert.Equal(t, 7, copyRoot.Len()) + }) } func TestTreeSplit(t *testing.T) { From 8be188604ff7890985f43b8f9f0d269f8f8de170 Mon Sep 17 00:00:00 2001 From: raararaara Date: Fri, 22 Mar 2024 10:20:49 +0900 Subject: [PATCH 08/14] Add test code related to `treeNode.insPrevId` patch --- pkg/document/crdt/tree_test.go | 55 ++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index b03feca2c..1d9f0ab05 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -417,21 +417,18 @@ func TestTreeEdit(t *testing.T) { assert.Equal(t, 0, idx) }) - t.Run("length test", func(t *testing.T) { + t.Run("reflects changes in treeNodes' length accurately with DeepCopy test", func(t *testing.T) { + // 01. Create a tree and insert a paragraph with text. root := helper.TestRoot() ctx := helper.TextChangeContext(root) - - // 01. Create a tree and insert a paragraph with text. tree := crdt.NewTree(crdt.NewTreeNode(helper.PosT(ctx), "r", nil), helper.TimeT(ctx)) assert.Equal(t, 0, tree.Root().Len()) assert.Equal(t, "", tree.ToXML()) - _, err := tree.EditT(0, 0, []*crdt.TreeNode{crdt.NewTreeNode(helper. PosT(ctx), "p", nil)}, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) assert.NoError(t, err) assert.Equal(t, "

", tree.ToXML()) assert.Equal(t, 2, tree.Root().Len()) - _, err = tree.EditT(1, 1, []*crdt.TreeNode{ crdt.NewTreeNode(helper.PosT(ctx), "text", nil, "hel_lo"), }, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) @@ -445,13 +442,59 @@ func TestTreeEdit(t *testing.T) { assert.Equal(t, "

hello

", tree.ToXML()) assert.Equal(t, 7, tree.Root().Len()) - // 03. Makes a deep copy of root. + // 03. Makes a deep copy of crdt.Tree.Root. copyRoot, err := tree.Root().DeepCopy() assert.NoError(t, err) // 04. Check if the length of the deep copied root is the same as the length of the original. assert.Equal(t, 7, copyRoot.Len()) }) + + // TODO: identify test name, define step + t.Run("insPrevId test", func(t *testing.T) { + root := helper.TestRoot() + ctx := helper.TextChangeContext(root) + tree := crdt.NewTree(crdt.NewTreeNode(helper.PosT(ctx), "r", nil), helper.TimeT(ctx)) + assert.Equal(t, 0, tree.Root().Len()) + assert.Equal(t, "", tree.ToXML()) + + pNode := crdt.NewTreeNode(helper.PosT(ctx), "p", nil) + _, err := tree.EditT(0, 0, []*crdt.TreeNode{pNode}, 0, + helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + _, err = tree.EditT(1, 1, []*crdt.TreeNode{ + crdt.NewTreeNode(helper.PosT(ctx), "text", nil, "hello"), + }, 0, + helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

hello

", tree.ToXML()) + + _, err = tree.EditT(3, 3, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

hello

", tree.ToXML()) + + left, err := pNode.Child(0) + assert.NoError(t, err) + right, err := pNode.Child(1) + assert.NoError(t, err) + + assert.Equal(t, left.InsNextID, right.ID) + assert.Equal(t, right.InsPrevID, left.ID) + + copyRoot, err := tree.Root().DeepCopy() + assert.NoError(t, err) + + para, err := copyRoot.Child(0) + assert.NoError(t, err) + + left, err = para.Child(0) + assert.NoError(t, err) + right, err = para.Child(1) + assert.NoError(t, err) + + assert.Equal(t, left.InsNextID, right.ID) + assert.Equal(t, right.InsPrevID, left.ID) + }) } func TestTreeSplit(t *testing.T) { From 70070b22d52506e9a26d6f0a7eb1cdc700d500e3 Mon Sep 17 00:00:00 2001 From: Youngteac Hong Date: Fri, 22 Mar 2024 12:44:03 +0900 Subject: [PATCH 09/14] Reflect feedback --- pkg/document/crdt/tree.go | 23 ++-- pkg/document/crdt/tree_test.go | 155 ++++++++++++----------- test/integration/main_test.go | 9 -- test/integration/tree_test.go | 219 --------------------------------- 4 files changed, 89 insertions(+), 317 deletions(-) diff --git a/pkg/document/crdt/tree.go b/pkg/document/crdt/tree.go index f2a63225c..2f0971cb5 100644 --- a/pkg/document/crdt/tree.go +++ b/pkg/document/crdt/tree.go @@ -123,8 +123,8 @@ func NewTreeNode(id *TreeNodeID, nodeType string, attributes *RHT, value ...stri return node } -// toIDString returns a string that can be used as an ID for this TreeNodeID. -func (t *TreeNodeID) toIDString() string { +// ToIDString returns a string that can be used as an ID for this TreeNodeID. +func (t *TreeNodeID) ToIDString() string { return t.CreatedAt.ToTestString() + ":" + strconv.Itoa(t.Offset) } @@ -353,17 +353,17 @@ func (n *TreeNode) InsertAt(newNode *TreeNode, offset int) error { // DeepCopy copies itself deeply. func (n *TreeNode) DeepCopy() (*TreeNode, error) { - var clone *TreeNode + var attrs *RHT if n.Attrs != nil { - clone = NewTreeNode(n.ID, n.Type(), n.Attrs.DeepCopy(), n.Value) - } else { - clone = NewTreeNode(n.ID, n.Type(), nil, n.Value) + attrs = n.Attrs.DeepCopy() } - clone.RemovedAt = n.RemovedAt - clone.Index.Length = n.Index.Length + clone := NewTreeNode(n.ID, n.Type(), attrs, n.Value) + clone.Index.Length = n.Index.Length + clone.RemovedAt = n.RemovedAt clone.InsPrevID = n.InsPrevID clone.InsNextID = n.InsNextID + if n.IsText() { return clone, nil } @@ -377,6 +377,7 @@ func (n *TreeNode) DeepCopy() (*TreeNode, error) { children = append(children, node.Index) } + if err := clone.Index.SetChildren(children); err != nil { return nil, err } @@ -470,7 +471,7 @@ func (t *Tree) purgeNode(node *TreeNode) error { node.InsPrevID = nil node.InsNextID = nil - delete(t.removedNodeMap, node.ID.toIDString()) + delete(t.removedNodeMap, node.ID.ToIDString()) return nil } @@ -650,7 +651,7 @@ func (t *Tree) Edit( // 02. Delete: delete the nodes that are marked as removed. for _, node := range toBeRemoveds { if node.remove(editedAt) { - t.removedNodeMap[node.ID.toIDString()] = node + t.removedNodeMap[node.ID.ToIDString()] = node } } @@ -701,7 +702,7 @@ func (t *Tree) Edit( createdAtMapByActor[actorIDHex] = createdAt } } - t.removedNodeMap[node.Value.ID.toIDString()] = node.Value + t.removedNodeMap[node.Value.ID.ToIDString()] = node.Value } t.NodeMapByID.Put(node.Value.ID, node.Value) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index 1d9f0ab05..a9b4c2d1f 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -17,6 +17,7 @@ package crdt_test import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -34,6 +35,49 @@ var ( } ) +func buildTreeHash(node *crdt.TreeNode) string { + builder := strings.Builder{} + builder.WriteString("(") + builder.WriteString(node.ID.ToIDString()) + for _, child := range node.Index.Children(true) { + builder.WriteString(buildTreeHash(child.Value)) + } + builder.WriteString(")") + return builder.String() +} + +func assertEqualTreeNode(t *testing.T, nodeA, nodeB *crdt.TreeNode) { + tupleA := buildTreeHash(nodeA) + tupleB := buildTreeHash(nodeB) + assert.Equal(t, tupleA, tupleB) + + // TODO(raararaara): Check the equality of the node's attributes. + // para, _ := tree.Root().Child(0) + // left, _ := para.Child(0) + // assert.NoError(t, err) + // right, err := para.Child(1) + // assert.NoError(t, err) + // assert.Equal(t, left.InsNextID, right.ID) + // assert.Equal(t, right.InsPrevID, left.ID) +} + +func createHelloTree(t *testing.T, ctx *change.Context) *crdt.Tree { + tree := crdt.NewTree(crdt.NewTreeNode(helper.PosT(ctx), "r", nil), helper.TimeT(ctx)) + _, err := tree.EditT(0, 0, []*crdt.TreeNode{ + crdt.NewTreeNode(helper.PosT(ctx), "p", nil), + }, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + + _, err = tree.EditT(1, 1, []*crdt.TreeNode{ + crdt.NewTreeNode(helper.PosT(ctx), "text", nil, "hello"), + }, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

hello

", tree.ToXML()) + assert.Equal(t, 7, tree.Root().Len()) + + return tree +} + func TestTreeNode(t *testing.T) { t.Run("text node test", func(t *testing.T) { node := crdt.NewTreeNode(dummyTreeNodeID, "text", nil, "hello") @@ -107,6 +151,39 @@ func TestTreeNode(t *testing.T) { assert.Equal(t, test.length-2, right.Len()) } }) + + t.Run("deepcopy test with deletion", func(t *testing.T) { + // 01. Create a tree with `

hello

` + ctx := helper.TextChangeContext(helper.TestRoot()) + tree := createHelloTree(t, ctx) + + // 02. Erase 3rd character from the text. + _, err := tree.EditT(4, 5, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

helo

", tree.ToXML()) + assert.Equal(t, 6, tree.Root().Len()) + + // 03. Make a deep copy of the root and check if the node is the same as the original. + clone, err := tree.Root().DeepCopy() + assert.NoError(t, err) + assertEqualTreeNode(t, tree.Root(), clone) + }) + + t.Run("deepcopy test with split", func(t *testing.T) { + // 01. Create a tree with `

hello

` + ctx := helper.TextChangeContext(helper.TestRoot()) + tree := createHelloTree(t, ctx) + + // 02. Split the text node at the 3rd character. + _, err := tree.EditT(3, 3, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) + assert.NoError(t, err) + assert.Equal(t, "

hello

", tree.ToXML()) + + // 03. Make a deep copy of the root and check if the node is the same as the original. + clone, err := tree.Root().DeepCopy() + assert.NoError(t, err) + assertEqualTreeNode(t, tree.Root(), clone) + }) } func TestTreeEdit(t *testing.T) { @@ -417,84 +494,6 @@ func TestTreeEdit(t *testing.T) { assert.Equal(t, 0, idx) }) - t.Run("reflects changes in treeNodes' length accurately with DeepCopy test", func(t *testing.T) { - // 01. Create a tree and insert a paragraph with text. - root := helper.TestRoot() - ctx := helper.TextChangeContext(root) - tree := crdt.NewTree(crdt.NewTreeNode(helper.PosT(ctx), "r", nil), helper.TimeT(ctx)) - assert.Equal(t, 0, tree.Root().Len()) - assert.Equal(t, "", tree.ToXML()) - _, err := tree.EditT(0, 0, []*crdt.TreeNode{crdt.NewTreeNode(helper. - PosT(ctx), "p", nil)}, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) - assert.NoError(t, err) - assert.Equal(t, "

", tree.ToXML()) - assert.Equal(t, 2, tree.Root().Len()) - _, err = tree.EditT(1, 1, []*crdt.TreeNode{ - crdt.NewTreeNode(helper.PosT(ctx), "text", nil, "hel_lo"), - }, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) - assert.NoError(t, err) - assert.Equal(t, "

hel_lo

", tree.ToXML()) - assert.Equal(t, 8, tree.Root().Len()) - - // 02. Erase 3rd character from the text. - _, err = tree.EditT(4, 5, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) - assert.NoError(t, err) - assert.Equal(t, "

hello

", tree.ToXML()) - assert.Equal(t, 7, tree.Root().Len()) - - // 03. Makes a deep copy of crdt.Tree.Root. - copyRoot, err := tree.Root().DeepCopy() - assert.NoError(t, err) - - // 04. Check if the length of the deep copied root is the same as the length of the original. - assert.Equal(t, 7, copyRoot.Len()) - }) - - // TODO: identify test name, define step - t.Run("insPrevId test", func(t *testing.T) { - root := helper.TestRoot() - ctx := helper.TextChangeContext(root) - tree := crdt.NewTree(crdt.NewTreeNode(helper.PosT(ctx), "r", nil), helper.TimeT(ctx)) - assert.Equal(t, 0, tree.Root().Len()) - assert.Equal(t, "", tree.ToXML()) - - pNode := crdt.NewTreeNode(helper.PosT(ctx), "p", nil) - _, err := tree.EditT(0, 0, []*crdt.TreeNode{pNode}, 0, - helper.TimeT(ctx), issueTimeTicket(ctx)) - assert.NoError(t, err) - _, err = tree.EditT(1, 1, []*crdt.TreeNode{ - crdt.NewTreeNode(helper.PosT(ctx), "text", nil, "hello"), - }, 0, - helper.TimeT(ctx), issueTimeTicket(ctx)) - assert.NoError(t, err) - assert.Equal(t, "

hello

", tree.ToXML()) - - _, err = tree.EditT(3, 3, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) - assert.NoError(t, err) - assert.Equal(t, "

hello

", tree.ToXML()) - - left, err := pNode.Child(0) - assert.NoError(t, err) - right, err := pNode.Child(1) - assert.NoError(t, err) - - assert.Equal(t, left.InsNextID, right.ID) - assert.Equal(t, right.InsPrevID, left.ID) - - copyRoot, err := tree.Root().DeepCopy() - assert.NoError(t, err) - - para, err := copyRoot.Child(0) - assert.NoError(t, err) - - left, err = para.Child(0) - assert.NoError(t, err) - right, err = para.Child(1) - assert.NoError(t, err) - - assert.Equal(t, left.InsNextID, right.ID) - assert.Equal(t, right.InsPrevID, left.ID) - }) } func TestTreeSplit(t *testing.T) { diff --git a/test/integration/main_test.go b/test/integration/main_test.go index 484a0c769..e63be3137 100644 --- a/test/integration/main_test.go +++ b/test/integration/main_test.go @@ -119,15 +119,6 @@ func syncClientsThenCheckEqual(t *testing.T, pairs []clientAndDocPair) bool { return true } -// activateSingleClient creates and activates a single client. -func activateSingleClient(t *testing.T) *client.Client { - c, err := client.Dial(defaultServer.RPCAddr()) - assert.NoError(t, err) - assert.NoError(t, c.Activate(context.Background())) - - return c -} - // activeClients creates and activates the given number of clients. func activeClients(t *testing.T, n int) (clients []*client.Client) { for i := 0; i < n; i++ { diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go index 5bd4f151e..b9726058b 100644 --- a/test/integration/tree_test.go +++ b/test/integration/tree_test.go @@ -792,225 +792,6 @@ func TestTree(t *testing.T) { assert.Equal(t, `{"type":"root","children":[{"type":"p","children":[{"type":"text","value":"ab"}]},{"type":"p","children":[{"type":"text","value":"cd"}]}]}`, doc.Root().GetTree("t").Marshal()) }) - // Concurrent editing, client reload, after edit test - t.Run("concurrently edit with client reload case test", func(t *testing.T) { - ctx := context.Background() - d1 := document.New(helper.TestDocKey(t)) - assert.NoError(t, c1.Attach(ctx, d1)) - d2 := document.New(helper.TestDocKey(t)) - assert.NoError(t, c2.Attach(ctx, d2)) - - const snapshotThreshold = 500 - - for idx := 0; idx < snapshotThreshold; idx++ { - assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - root.SetInteger("i", 0) - - return nil - })) - } - - assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - root.SetNewTree("t", &json.TreeNode{ - Type: "r", - Children: []json.TreeNode{{ - Type: "c", - Children: []json.TreeNode{{ - Type: "u", Children: []json.TreeNode{{ - Type: "p", - Children: []json.TreeNode{{ - Type: "n", - Children: []json.TreeNode{}, - }}, - }}, - }}, - }, - { - Type: "c", - Children: []json.TreeNode{{ - Type: "p", - Children: []json.TreeNode{{ - Type: "n", - Children: []json.TreeNode{}, - }}, - }}, - }}, - }) - return nil - })) - assert.NoError(t, c1.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 0}, []int{1, 0, 0, 0}, &json.TreeNode{ - Type: "text", - Value: "1", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 1}, &json.TreeNode{ - Type: "text", - Value: "2", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ - Type: "text", - Value: "3", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ - Type: "text", - Value: " ", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 3}, &json.TreeNode{ - Type: "text", - Value: "네이버랑 ", - }, 0) - return nil - })) - - assert.NoError(t, c1.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

12 네이버랑 3

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

12 네이버랑 3

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 1}, []int{1, 0, 0, 8}, &json.TreeNode{ - Type: "text", - Value: " 2 네이버랑 ", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 2}, &json.TreeNode{ - Type: "text", - Value: "ㅋ", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, &json.TreeNode{ - Type: "text", - Value: "카", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, &json.TreeNode{ - Type: "text", - Value: "캌", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, &json.TreeNode{ - Type: "text", - Value: "카카", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 4}, &json.TreeNode{ - Type: "text", - Value: "캉", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 4}, &json.TreeNode{ - Type: "text", - Value: "카오", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 4}, []int{1, 0, 0, 5}, &json.TreeNode{ - Type: "text", - Value: "올", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 4}, []int{1, 0, 0, 5}, &json.TreeNode{ - Type: "text", - Value: "오라", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 5}, []int{1, 0, 0, 6}, &json.TreeNode{ - Type: "text", - Value: "랑", - }, 0) - - root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 6}, &json.TreeNode{ - Type: "text", - Value: " ", - }, 0) - - return nil - })) - - assert.NoError(t, c2.Sync(ctx)) - assert.NoError(t, c1.Sync(ctx)) - assert.Equal(t, "

1 카카오랑 2 네이버랑 3

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1 카카오랑 2 네이버랑 3

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 13}, []int{1, 0, 0, 14}, nil, 0) - root.GetTree("t").EditByPath([]int{1, 0, 0, 12}, []int{1, 0, 0, 13}, nil, 0) - - return nil - })) - - assert.NoError(t, c1.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

1 카카오랑 2 네이버3

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1 카카오랑 2 네이버3

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d2.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 6}, []int{1, 0, 0, 7}, nil, 0) - root.GetTree("t").EditByPath([]int{1, 0, 0, 5}, []int{1, 0, 0, 6}, nil, 0) - - return nil - })) - - assert.NoError(t, c2.Sync(ctx)) - assert.NoError(t, c1.Sync(ctx)) - assert.Equal(t, "

1 카카오2 네이버3

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1 카카오2 네이버3

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d1.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 9}, []int{1, 0, 0, 10}, nil, 0) - - return nil - })) - - assert.NoError(t, c1.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

1 카카오2 네이3

", d1.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1 카카오2 네이3

", d2.Root().GetTree("t").ToXML()) - - c3 := activateSingleClient(t) - d3 := document.New(helper.TestDocKey(t)) - assert.NoError(t, c3.Attach(ctx, d3)) - - assert.Equal(t, "

1 카카오2 네이3

", d3.Root().GetTree("t").ToXML()) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

1 카카오2 네이3

", d3.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1 카카오2 네이3

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d3.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 4}, []int{1, 0, 0, 5}, nil, 0) - root.GetTree("t").EditByPath([]int{1, 0, 0, 3}, []int{1, 0, 0, 4}, nil, 0) - - return nil - })) - - assert.NoError(t, c3.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

1 카2 네이3

", d3.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1 카2 네이3

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, d3.Update(func(root *json.Object, p *presence.Presence) error { - root.GetTree("t").EditByPath([]int{1, 0, 0, 2}, []int{1, 0, 0, 3}, nil, 0) - - return nil - })) - - assert.NoError(t, c3.Sync(ctx)) - assert.NoError(t, c2.Sync(ctx)) - assert.Equal(t, "

1 2 네이3

", d3.Root().GetTree("t").ToXML()) - assert.Equal(t, "

1 2 네이3

", d2.Root().GetTree("t").ToXML()) - - assert.NoError(t, c3.Deactivate(context.Background())) - assert.NoError(t, c3.Close()) - }) - // Concurrent editing, overlapping range test t.Run("concurrently delete overlapping elements test", func(t *testing.T) { ctx := context.Background() From e7a770cc878b4ad385f1b1082fc1324efc917fa1 Mon Sep 17 00:00:00 2001 From: raararaara Date: Fri, 22 Mar 2024 15:59:58 +0900 Subject: [PATCH 10/14] Add treeNode equality check function --- pkg/document/crdt/tree_test.go | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index a9b4c2d1f..7d0516bed 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -35,6 +35,11 @@ var ( } ) +type treeNodePair struct { + treeNode *crdt.TreeNode + parentNodeID *crdt.TreeNodeID +} + func buildTreeHash(node *crdt.TreeNode) string { builder := strings.Builder{} builder.WriteString("(") @@ -46,19 +51,24 @@ func buildTreeHash(node *crdt.TreeNode) string { return builder.String() } +func CollectNodesWithParentID(node *crdt.TreeNode, parentNodeID *crdt.TreeNodeID) []treeNodePair { + var list []treeNodePair + + list = append(list, treeNodePair{node, parentNodeID}) + for _, child := range node.Index.Children(true) { + list = append(list, CollectNodesWithParentID(child.Value, node.ID)...) + } + return list +} + func assertEqualTreeNode(t *testing.T, nodeA, nodeB *crdt.TreeNode) { - tupleA := buildTreeHash(nodeA) - tupleB := buildTreeHash(nodeB) - assert.Equal(t, tupleA, tupleB) - - // TODO(raararaara): Check the equality of the node's attributes. - // para, _ := tree.Root().Child(0) - // left, _ := para.Child(0) - // assert.NoError(t, err) - // right, err := para.Child(1) - // assert.NoError(t, err) - // assert.Equal(t, left.InsNextID, right.ID) - // assert.Equal(t, right.InsPrevID, left.ID) + //tupleA := buildTreeHash(nodeA) + //tupleB := buildTreeHash(nodeB) + //assert.Equal(t, tupleA, tupleB) + + listA := CollectNodesWithParentID(nodeA, nil) + listB := CollectNodesWithParentID(nodeB, nil) + assert.Equal(t, listA, listB) } func createHelloTree(t *testing.T, ctx *change.Context) *crdt.Tree { From 4801265c2ea4f9ebdffd944350c872926de826e9 Mon Sep 17 00:00:00 2001 From: raararaara Date: Fri, 22 Mar 2024 16:01:14 +0900 Subject: [PATCH 11/14] Lint --- pkg/document/crdt/tree_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index 7d0516bed..534c9ed60 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -35,7 +35,7 @@ var ( } ) -type treeNodePair struct { +type TreeNodePair struct { treeNode *crdt.TreeNode parentNodeID *crdt.TreeNodeID } @@ -51,10 +51,10 @@ func buildTreeHash(node *crdt.TreeNode) string { return builder.String() } -func CollectNodesWithParentID(node *crdt.TreeNode, parentNodeID *crdt.TreeNodeID) []treeNodePair { - var list []treeNodePair +func CollectNodesWithParentID(node *crdt.TreeNode, parentNodeID *crdt.TreeNodeID) []TreeNodePair { + var list []TreeNodePair - list = append(list, treeNodePair{node, parentNodeID}) + list = append(list, TreeNodePair{node, parentNodeID}) for _, child := range node.Index.Children(true) { list = append(list, CollectNodesWithParentID(child.Value, node.ID)...) } From a74b0c13e94c11ebf97c7b11af4b4687a5a9edb6 Mon Sep 17 00:00:00 2001 From: raararaara Date: Fri, 22 Mar 2024 16:43:14 +0900 Subject: [PATCH 12/14] Code cleanup --- pkg/document/crdt/tree_test.go | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index 534c9ed60..4198cae82 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -17,7 +17,6 @@ package crdt_test import ( - "strings" "testing" "github.com/stretchr/testify/assert" @@ -40,17 +39,6 @@ type TreeNodePair struct { parentNodeID *crdt.TreeNodeID } -func buildTreeHash(node *crdt.TreeNode) string { - builder := strings.Builder{} - builder.WriteString("(") - builder.WriteString(node.ID.ToIDString()) - for _, child := range node.Index.Children(true) { - builder.WriteString(buildTreeHash(child.Value)) - } - builder.WriteString(")") - return builder.String() -} - func CollectNodesWithParentID(node *crdt.TreeNode, parentNodeID *crdt.TreeNodeID) []TreeNodePair { var list []TreeNodePair @@ -62,10 +50,6 @@ func CollectNodesWithParentID(node *crdt.TreeNode, parentNodeID *crdt.TreeNodeID } func assertEqualTreeNode(t *testing.T, nodeA, nodeB *crdt.TreeNode) { - //tupleA := buildTreeHash(nodeA) - //tupleB := buildTreeHash(nodeB) - //assert.Equal(t, tupleA, tupleB) - listA := CollectNodesWithParentID(nodeA, nil) listB := CollectNodesWithParentID(nodeB, nil) assert.Equal(t, listA, listB) From f0cda641f5e89811d620af258dac29eef890df1f Mon Sep 17 00:00:00 2001 From: Youngteac Hong Date: Fri, 22 Mar 2024 17:06:10 +0900 Subject: [PATCH 13/14] Reflect feedback --- pkg/document/crdt/tree.go | 10 +++++----- pkg/document/crdt/tree_test.go | 27 ++++----------------------- test/helper/helper.go | 22 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/pkg/document/crdt/tree.go b/pkg/document/crdt/tree.go index 2f0971cb5..bf74090a1 100644 --- a/pkg/document/crdt/tree.go +++ b/pkg/document/crdt/tree.go @@ -123,8 +123,8 @@ func NewTreeNode(id *TreeNodeID, nodeType string, attributes *RHT, value ...stri return node } -// ToIDString returns a string that can be used as an ID for this TreeNodeID. -func (t *TreeNodeID) ToIDString() string { +// toIDString returns a string that can be used as an ID for this TreeNodeID. +func (t *TreeNodeID) toIDString() string { return t.CreatedAt.ToTestString() + ":" + strconv.Itoa(t.Offset) } @@ -471,7 +471,7 @@ func (t *Tree) purgeNode(node *TreeNode) error { node.InsPrevID = nil node.InsNextID = nil - delete(t.removedNodeMap, node.ID.ToIDString()) + delete(t.removedNodeMap, node.ID.toIDString()) return nil } @@ -651,7 +651,7 @@ func (t *Tree) Edit( // 02. Delete: delete the nodes that are marked as removed. for _, node := range toBeRemoveds { if node.remove(editedAt) { - t.removedNodeMap[node.ID.ToIDString()] = node + t.removedNodeMap[node.ID.toIDString()] = node } } @@ -702,7 +702,7 @@ func (t *Tree) Edit( createdAtMapByActor[actorIDHex] = createdAt } } - t.removedNodeMap[node.Value.ID.ToIDString()] = node.Value + t.removedNodeMap[node.Value.ID.toIDString()] = node.Value } t.NodeMapByID.Put(node.Value.ID, node.Value) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index 4198cae82..b72ce1a35 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -34,28 +34,9 @@ var ( } ) -type TreeNodePair struct { - treeNode *crdt.TreeNode - parentNodeID *crdt.TreeNodeID -} - -func CollectNodesWithParentID(node *crdt.TreeNode, parentNodeID *crdt.TreeNodeID) []TreeNodePair { - var list []TreeNodePair - - list = append(list, TreeNodePair{node, parentNodeID}) - for _, child := range node.Index.Children(true) { - list = append(list, CollectNodesWithParentID(child.Value, node.ID)...) - } - return list -} - -func assertEqualTreeNode(t *testing.T, nodeA, nodeB *crdt.TreeNode) { - listA := CollectNodesWithParentID(nodeA, nil) - listB := CollectNodesWithParentID(nodeB, nil) - assert.Equal(t, listA, listB) -} - func createHelloTree(t *testing.T, ctx *change.Context) *crdt.Tree { + // TODO(raararaara): This test should be generalized. e.g) createTree(ctx, "

hello

") + // https://pkg.go.dev/encoding/xml#Unmarshal tree := crdt.NewTree(crdt.NewTreeNode(helper.PosT(ctx), "r", nil), helper.TimeT(ctx)) _, err := tree.EditT(0, 0, []*crdt.TreeNode{ crdt.NewTreeNode(helper.PosT(ctx), "p", nil), @@ -160,7 +141,7 @@ func TestTreeNode(t *testing.T) { // 03. Make a deep copy of the root and check if the node is the same as the original. clone, err := tree.Root().DeepCopy() assert.NoError(t, err) - assertEqualTreeNode(t, tree.Root(), clone) + helper.AssertEqualTreeNode(t, tree.Root(), clone) }) t.Run("deepcopy test with split", func(t *testing.T) { @@ -176,7 +157,7 @@ func TestTreeNode(t *testing.T) { // 03. Make a deep copy of the root and check if the node is the same as the original. clone, err := tree.Root().DeepCopy() assert.NoError(t, err) - assertEqualTreeNode(t, tree.Root(), clone) + helper.AssertEqualTreeNode(t, tree.Root(), clone) }) } diff --git a/test/helper/helper.go b/test/helper/helper.go index 1dc282b74..92d46ec2e 100644 --- a/test/helper/helper.go +++ b/test/helper/helper.go @@ -198,6 +198,28 @@ func BuildTreeNode(node *json.TreeNode) *crdt.TreeNode { return doc.Root().GetTree("test").Root() } +type treeNodePair struct { + node *crdt.TreeNode + parentID *crdt.TreeNodeID +} + +func createTreeNodePairs(node *crdt.TreeNode, parentID *crdt.TreeNodeID) []treeNodePair { + var pairs []treeNodePair + + pairs = append(pairs, treeNodePair{node, parentID}) + for _, child := range node.Index.Children(true) { + pairs = append(pairs, createTreeNodePairs(child.Value, node.ID)...) + } + return pairs +} + +// AssertEqualTreeNode asserts that the given TreeNodes are equal. +func AssertEqualTreeNode(t *testing.T, nodeA, nodeB *crdt.TreeNode) { + pairsA := createTreeNodePairs(nodeA, nil) + pairsB := createTreeNodePairs(nodeB, nil) + assert.Equal(t, pairsA, pairsB) +} + var portOffset = 0 // TestConfig returns config for creating Yorkie instance. From a0b856e264abc1412864e08922ac9e8e60ecce75 Mon Sep 17 00:00:00 2001 From: Youngteac Hong Date: Fri, 22 Mar 2024 17:12:23 +0900 Subject: [PATCH 14/14] Reflect feedback --- pkg/document/crdt/tree_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pkg/document/crdt/tree_test.go b/pkg/document/crdt/tree_test.go index b72ce1a35..9297f8a32 100644 --- a/pkg/document/crdt/tree_test.go +++ b/pkg/document/crdt/tree_test.go @@ -128,33 +128,29 @@ func TestTreeNode(t *testing.T) { }) t.Run("deepcopy test with deletion", func(t *testing.T) { - // 01. Create a tree with `

hello

` ctx := helper.TextChangeContext(helper.TestRoot()) tree := createHelloTree(t, ctx) - // 02. Erase 3rd character from the text. + // To make tree have a deletion to check length modification. _, err := tree.EditT(4, 5, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) assert.NoError(t, err) assert.Equal(t, "

helo

", tree.ToXML()) assert.Equal(t, 6, tree.Root().Len()) - // 03. Make a deep copy of the root and check if the node is the same as the original. clone, err := tree.Root().DeepCopy() assert.NoError(t, err) helper.AssertEqualTreeNode(t, tree.Root(), clone) }) t.Run("deepcopy test with split", func(t *testing.T) { - // 01. Create a tree with `

hello

` ctx := helper.TextChangeContext(helper.TestRoot()) tree := createHelloTree(t, ctx) - // 02. Split the text node at the 3rd character. + // To make tree have split text nodes. _, err := tree.EditT(3, 3, nil, 0, helper.TimeT(ctx), issueTimeTicket(ctx)) assert.NoError(t, err) assert.Equal(t, "

hello

", tree.ToXML()) - // 03. Make a deep copy of the root and check if the node is the same as the original. clone, err := tree.Root().DeepCopy() assert.NoError(t, err) helper.AssertEqualTreeNode(t, tree.Root(), clone)