diff --git a/test/integration/tree_concurrency_test.go b/test/integration/tree_concurrency_test.go
index b62e41706..09c0a55b5 100644
--- a/test/integration/tree_concurrency_test.go
+++ b/test/integration/tree_concurrency_test.go
@@ -30,6 +30,24 @@ import (
"github.com/yorkie-team/yorkie/test/helper"
)
+func parseSimpleXML(s string) []string {
+ res := []string{}
+ for i := 0; i < len(s); i++ {
+ now := ``
+ if s[i] == '<' {
+ for i < len(s) && s[i] != '>' {
+ now += string(s[i])
+ i++
+ }
+ now += string(s[i])
+ } else {
+ now += string(s[i])
+ }
+ res = append(res, now)
+ }
+ return res
+}
+
type testResult struct {
flag bool
resultDesc string
@@ -43,6 +61,8 @@ const (
RangeMiddle
RangeBack
RangeAll
+ RangeOneQuarter
+ RangeThreeQuarter
)
type rangeType struct {
@@ -59,14 +79,22 @@ type twoRangesType struct {
}
func getRange(ranges twoRangesType, selector rangeSelector, user int) rangeType {
+ interval := ranges.ranges[user]
+ from, mid, to := interval.from, interval.mid, interval.to
if selector == RangeFront {
- return rangeType{ranges.ranges[user].from, ranges.ranges[user].from}
+ return rangeType{from, from}
} else if selector == RangeMiddle {
- return rangeType{ranges.ranges[user].mid, ranges.ranges[user].mid}
+ return rangeType{mid, mid}
} else if selector == RangeBack {
- return rangeType{ranges.ranges[user].to, ranges.ranges[user].to}
+ return rangeType{to, to}
} else if selector == RangeAll {
- return rangeType{ranges.ranges[user].from, ranges.ranges[user].to}
+ return rangeType{from, to}
+ } else if selector == RangeOneQuarter {
+ pos := (from + mid + 1) / 2
+ return rangeType{pos, pos}
+ } else if selector == RangeThreeQuarter {
+ pos := (mid + to) / 2
+ return rangeType{pos, pos}
}
return rangeType{-1, -1}
}
@@ -77,6 +105,20 @@ func makeTwoRanges(from1, mid1, to1 int, from2, mid2, to2 int, desc string) twoR
return twoRangesType{[2]rangeWithMiddleType{range0, range1}, desc}
}
+func getMergeRange(xml string, interval rangeType) rangeType {
+ content := parseSimpleXML(xml)
+ st, ed := -1, -1
+ for i := interval.from + 1; i <= interval.to; i++ {
+ if st == -1 && len(content[i]) >= 2 && content[i][0] == '<' && content[i][1] == '/' {
+ st = i - 1
+ }
+ if len(content[i]) >= 2 && content[i][0] == '<' && content[i][1] != '/' {
+ ed = i
+ }
+ }
+ return rangeType{st, ed}
+}
+
type styleOpCode int
type editOpCode int
@@ -89,6 +131,8 @@ const (
const (
EditUndefined editOpCode = iota
EditUpdate
+ MergeUpdate
+ SplitUpdate
)
type operationInterface interface {
@@ -138,7 +182,19 @@ func (op editOperationType) run(t *testing.T, doc *document.Document, user int,
from, to := interval.from, interval.to
assert.NoError(t, doc.Update(func(root *json.Object, p *presence.Presence) error {
- root.GetTree("t").Edit(from, to, op.content, op.splitLevel)
+ if op.op == EditUpdate {
+ root.GetTree("t").Edit(from, to, op.content, op.splitLevel)
+ } else if op.op == MergeUpdate {
+ mergeInterval := getMergeRange(root.GetTree("t").ToXML(), interval)
+ from, to = mergeInterval.from, mergeInterval.to
+ if from != -1 && to != -1 && from < to {
+ root.GetTree("t").Edit(mergeInterval.from, mergeInterval.to, op.content, op.splitLevel)
+ }
+ } else if op.op == SplitUpdate {
+ assert.NotEqual(t, 0, op.splitLevel)
+ assert.Equal(t, from, to)
+ root.GetTree("t").Edit(from, to, op.content, op.splitLevel)
+ }
return nil
}))
}
@@ -247,6 +303,7 @@ func TestTreeConcurrencyEditEdit(t *testing.T) {
editOperationType{RangeBack, EditUpdate, elementNode1, 0, `insertElementBack`},
editOperationType{RangeAll, EditUpdate, elementNode1, 0, `replaceElement`},
editOperationType{RangeAll, EditUpdate, nil, 0, `delete`},
+ editOperationType{RangeAll, MergeUpdate, nil, 0, `merge`},
}
editOperations2 := []operationInterface{
@@ -259,11 +316,119 @@ func TestTreeConcurrencyEditEdit(t *testing.T) {
editOperationType{RangeBack, EditUpdate, elementNode2, 0, `insertElementBack`},
editOperationType{RangeAll, EditUpdate, elementNode2, 0, `replaceElement`},
editOperationType{RangeAll, EditUpdate, nil, 0, `delete`},
+ editOperationType{RangeAll, MergeUpdate, nil, 0, `merge`},
}
RunTestTreeConcurrency("concurrently-edit-edit-test", t, initialState, initialXML, ranges, editOperations1, editOperations2)
}
+func TestTreeConcurrencySplitSplit(t *testing.T) {
+ // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
+ // a b c d e f g h
i j k l
+ + initialState := json.TreeNode{ + Type: "root", + Children: []json.TreeNode{ + {Type: "p", Children: []json.TreeNode{ + {Type: "p", Children: []json.TreeNode{ + {Type: "p", Children: []json.TreeNode{ + {Type: "p", Children: []json.TreeNode{{Type: "text", Value: "abcd"}}}, + {Type: "p", Children: []json.TreeNode{{Type: "text", Value: "efgh"}}}, + }}, + {Type: "p", Children: []json.TreeNode{{Type: "text", Value: "ijkl"}}}, + }}, + }}, + }, + } + initialXML := `abcd
efgh
ijkl
abcd
+ makeTwoRanges(3, 6, 9, 3, 6, 9, `equal-single`), + // equal-multiple-element:abcd
efgh
+ makeTwoRanges(3, 9, 15, 3, 9, 15, `equal-multiple`), + // A contains B same level:abcd
efgh
-efgh
+ makeTwoRanges(3, 9, 15, 9, 12, 15, `A contains B same level`), + // A contains B multiple level:abcd
efgh
ijkl
-efgh
+ makeTwoRanges(2, 16, 22, 9, 12, 15, `A contains B multiple level`), + // side by side + makeTwoRanges(3, 6, 9, 9, 12, 15, `B is next to A`), + } + + splitOperations := []operationInterface{ + editOperationType{RangeFront, SplitUpdate, nil, 1, `split-front-1`}, + editOperationType{RangeOneQuarter, SplitUpdate, nil, 1, `split-one-quarter-1`}, + editOperationType{RangeThreeQuarter, SplitUpdate, nil, 1, `split-three-quarter-1`}, + editOperationType{RangeBack, SplitUpdate, nil, 1, `split-back-1`}, + editOperationType{RangeFront, SplitUpdate, nil, 2, `split-front-2`}, + editOperationType{RangeOneQuarter, SplitUpdate, nil, 2, `split-one-quarter-2`}, + editOperationType{RangeThreeQuarter, SplitUpdate, nil, 2, `split-three-quarter-2`}, + editOperationType{RangeBack, SplitUpdate, nil, 2, `split-back-2`}, + } + + RunTestTreeConcurrency("concurrently-split-split-test", t, initialState, initialXML, ranges, splitOperations, splitOperations) +} + +func TestTreeConcurrencySplitEdit(t *testing.T) { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 + //
a b c d
e f g h
i j k l
abcd
efgh
ijkl
ab'cd
+ makeTwoRanges(2, 5, 8, 2, 5, 8, `equal`), + // A contains B:ab'cd
- bc + makeTwoRanges(2, 5, 8, 4, 5, 6, `A contains B`), + // B contains A:ab'cd
-abcd
efgh
+ makeTwoRanges(2, 5, 8, 2, 8, 14, `B contains A`), + // left node(text):ab'cd
- ab + makeTwoRanges(2, 5, 8, 3, 4, 5, `left node(text)`), + // right node(text):ab'cd
- cd + makeTwoRanges(2, 5, 8, 5, 6, 7, `right node(text)`), + // left node(element):abcd
'efgh
-abcd
+ makeTwoRanges(2, 8, 14, 2, 5, 8, `left node(element)`), + // right node(element):abcd
'efgh
-efgh
+ makeTwoRanges(2, 8, 14, 8, 11, 14, `right node(element)`), + // A -> B:ab'cd
-efgh
+ makeTwoRanges(2, 5, 8, 8, 11, 14, `A -> B`), + // B -> A:ef'gh
-abcd
+ makeTwoRanges(8, 11, 14, 2, 5, 8, `B -> A`), + } + + splitOperations := []operationInterface{ + editOperationType{RangeMiddle, SplitUpdate, nil, 1, `split-1`}, + editOperationType{RangeMiddle, SplitUpdate, nil, 2, `split-2`}, + } + + editOperations := []operationInterface{ + editOperationType{RangeFront, EditUpdate, content, 0, `insertFront`}, + editOperationType{RangeMiddle, EditUpdate, content, 0, `insertMiddle`}, + editOperationType{RangeBack, EditUpdate, content, 0, `insertBack`}, + editOperationType{RangeAll, EditUpdate, content, 0, "replace"}, + editOperationType{RangeAll, EditUpdate, nil, 0, `delete`}, + editOperationType{RangeAll, MergeUpdate, nil, 0, `merge`}, + styleOperationType{RangeAll, StyleSet, "bold", "aa", `style`}, + styleOperationType{RangeAll, StyleRemove, "italic", "", `remove-style`}, + } + + RunTestTreeConcurrency("concurrently-split-edit-test", t, initialState, initialXML, ranges, splitOperations, editOperations) +} + func TestTreeConcurrencyStyleStyle(t *testing.T) { // 0 1 2 3 4 5 6 7 8 9 //a
b
c
a
b
c
a
b
c
b
-b
makeTwoRanges(3, 3, 6, 3, -1, 6, `equal`), + // equal multiple:a
b
c
-a
b
c
+ makeTwoRanges(0, 3, 9, 0, 3, 9, `equal multiple`), // A contains B:a
b
c
-b
makeTwoRanges(0, 3, 9, 3, -1, 6, `A contains B`), // B contains A:b
-a
b
c
@@ -344,10 +511,11 @@ func TestTreeConcurrencyEditStyle(t *testing.T) { editOperationType{RangeBack, EditUpdate, content, 0, `insertBack`}, editOperationType{RangeAll, EditUpdate, nil, 0, `delete`}, editOperationType{RangeAll, EditUpdate, content, 0, `replace`}, + editOperationType{RangeAll, MergeUpdate, nil, 0, `merge`}, } styleOperations := []operationInterface{ - styleOperationType{RangeAll, StyleRemove, "bold", "", `remove-bold`}, + styleOperationType{RangeAll, StyleRemove, "color", "", `remove-bold`}, styleOperationType{RangeAll, StyleSet, "bold", "aa", `set-bold-aa`}, } diff --git a/test/integration/tree_test.go b/test/integration/tree_test.go index 38df94629..5afb68c8d 100644 --- a/test/integration/tree_test.go +++ b/test/integration/tree_test.go @@ -1343,7 +1343,7 @@ func TestTree(t *testing.T) { assert.Equal(t, "a
b
a
b