Skip to content

Commit

Permalink
Add merge and split concurrency tests (#780)
Browse files Browse the repository at this point in the history
* **[ADDED]** Split and Split(5 * 8 * 8 = 320 cases)
  - Ranges(5)
    - Equals, single element
    - Equals, multiple element
    - A contains B, same level
    - A contains B, multiple level
    - B is next to A
  - Operations for both A and B(8)
    - Split front of range, level = 1
    - Split front of range, level = 2
    - Split one-quarter of range, level = 1
    - Split one-quarter of range, level = 2
    - Split three-quarter of range, level = 1
    - Split three-quarter of range, level = 2
    - Split back of range, level = 1
    - Split back of range, level = 2
* **[ADDED]** Split and Edit(9 * 2 * 8 = 144 cases):
  - Ranges(9)
    - Equals
    - A contains B
    - B contains A
    - Left node(text) of split node
    - Right node(text) of split node
    - Left node(element) of split node
    - Right node(element) of split node
    - B is next to A
    - A is next to B
  - Operations for A(2)
    - splitLevel = 1
    - splitLevel = 2
  - Operations for B(8)
    - Insert front of range
    - Insert back of range
    - Insert middle of range
    - Delete
    - Replace
    - Merge
    - Style
    - RemoveStyle

Failure cases (0 + 15 + 3 + 60 + 46 = 124/1592 cases)
  - Style and Style (0/144 cases)
  - Edit and Style (15/84 cases)
    - (6) {equal, equal-multiple} / {insert-front, insert-middle, replace} / style
    - (4) {intersect, A -> B} / {insert-middle, insert-back} / style
    - (1) A contains B / insert-middle / style
    - (4) B contains A / {insert-front, insert-middle, insert-back, replace} / style
  - Edit and Edit (3/900 cases)
    - (1) intersect-element / merge / replace-text
      - d1: `<root><p>abcdef</p>B</root>`
      - d2: `<root><p>abc</p>B</root>`
    - (1) intersect-element / merge / replace-element
      - d1: `<root><p>abcdef</p><i></i></root>`
      - d2: `<root><p>abc</p><i></i></root>`
    - (1) intersect-element / merge / delete
      - d1: `<root><p>abcdef</p></root>`
      - d2: `<root><p>abc</p></root>`
  - Split and Split (60/320 cases)
    - (1) equal-single / split-one-quarter-1 / split-back-1
    - (1) equal-single / split-three-quarter-1 / split-back-1
    - (1) equal-multiple / split-three-quarter-1 / split-back-1
    - (1) A contains B same level / split-one-quarter-1 / split-front-1
    - (1) A contains B same level / split-three-quarter-1 / split-back-1
    - (1) A -> B / split-one-quarter-1 / split-front-1
    - (1) A -> B / split-three-quarter-1 / split-back-1
    - (53) and many cases with splitLevel > 1
  - Split and Edit (46/144 cases)
    - (6) equals / {split-1, split-2} / {insert-back, replace, delete}
    - (8) A contains B / {split-1, split-2} / {replace, delete, style, remove-style}
    - (8) B contains A / {split-1, split-2} / {insert-middle, replace, delete, style}
    - (1) B contains A / split-2 / remove-style
    - (8) {right node text, right node element} / {split-1, split-2} / {style, remove-style}
    - (4) {right node text, right node element} / split-2 / {replace, delete}
    - (8) A -> B / {split-1, split-2} / {insert-front, replace, delete, style}
    - (1) A -> B / split-2 / remove-style
    - (2) B -> A / split-2 / {replace, delete}
  • Loading branch information
justiceHui authored Feb 6, 2024
1 parent 67b16a1 commit 94d6e30
Show file tree
Hide file tree
Showing 2 changed files with 180 additions and 12 deletions.
188 changes: 178 additions & 10 deletions test/integration/tree_concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,6 +61,8 @@ const (
RangeMiddle
RangeBack
RangeAll
RangeOneQuarter
RangeThreeQuarter
)

type rangeType struct {
Expand All @@ -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}
}
Expand All @@ -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

Expand All @@ -89,6 +131,8 @@ const (
const (
EditUndefined editOpCode = iota
EditUpdate
MergeUpdate
SplitUpdate
)

type operationInterface interface {
Expand Down Expand Up @@ -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
}))
}
Expand Down Expand Up @@ -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{
Expand All @@ -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
// <root> <p> <p> <p> <p> a b c d </p> <p> e f g h </p> </p> <p> i j k l </p> </p> </p> </root>

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 := `<root><p><p><p><p>abcd</p><p>efgh</p></p><p>ijkl</p></p></p></root>`

ranges := []twoRangesType{
// equal-single-element: <p>abcd</p>
makeTwoRanges(3, 6, 9, 3, 6, 9, `equal-single`),
// equal-multiple-element: <p>abcd</p><p>efgh</p>
makeTwoRanges(3, 9, 15, 3, 9, 15, `equal-multiple`),
// A contains B same level: <p>abcd</p><p>efgh</p> - <p>efgh</p>
makeTwoRanges(3, 9, 15, 9, 12, 15, `A contains B same level`),
// A contains B multiple level: <p><p>abcd</p><p>efgh</p></p><p>ijkl</p> - <p>efgh</p>
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
// <root> <p> <p> <p> a b c d </p> <p> e f g h </p> </p> <p> i j k l </p> </p> </root>

initialState := json.TreeNode{
Type: "root",
Children: []json.TreeNode{
{Type: "p", Children: []json.TreeNode{
{Type: "p", Children: []json.TreeNode{
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "abcd"}}, Attributes: map[string]string{"italic": "true"}},
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "efgh"}}, Attributes: map[string]string{"italic": "true"}},
}, Attributes: map[string]string{"italic": "true"}},
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "ijkl"}}, Attributes: map[string]string{"italic": "true"}},
}},
},
}
initialXML := `<root><p><p italic="true"><p italic="true">abcd</p><p italic="true">efgh</p></p><p italic="true">ijkl</p></p></root>`

content := &json.TreeNode{Type: "i", Children: []json.TreeNode{}}

ranges := []twoRangesType{
// equal: <p>ab'cd</p>
makeTwoRanges(2, 5, 8, 2, 5, 8, `equal`),
// A contains B: <p>ab'cd</p> - bc
makeTwoRanges(2, 5, 8, 4, 5, 6, `A contains B`),
// B contains A: <p>ab'cd</p> - <p>abcd</p><p>efgh</p>
makeTwoRanges(2, 5, 8, 2, 8, 14, `B contains A`),
// left node(text): <p>ab'cd</p> - ab
makeTwoRanges(2, 5, 8, 3, 4, 5, `left node(text)`),
// right node(text): <p>ab'cd</p> - cd
makeTwoRanges(2, 5, 8, 5, 6, 7, `right node(text)`),
// left node(element): <p>abcd</p>'<p>efgh</p> - <p>abcd</p>
makeTwoRanges(2, 8, 14, 2, 5, 8, `left node(element)`),
// right node(element): <p>abcd</p>'<p>efgh</p> - <p>efgh</p>
makeTwoRanges(2, 8, 14, 8, 11, 14, `right node(element)`),
// A -> B: <p>ab'cd</p> - <p>efgh</p>
makeTwoRanges(2, 5, 8, 8, 11, 14, `A -> B`),
// B -> A: <p>ef'gh</p> - <p>abcd</p>
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
// <root> <p> a </p> <p> b </p> <p> c </p> </root>
Expand Down Expand Up @@ -314,18 +479,20 @@ func TestTreeConcurrencyEditStyle(t *testing.T) {
initialState := json.TreeNode{
Type: "root",
Children: []json.TreeNode{
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "a"}}},
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "b"}}},
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "c"}}},
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "a"}}, Attributes: map[string]string{"color": "red"}},
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "b"}}, Attributes: map[string]string{"color": "red"}},
{Type: "p", Children: []json.TreeNode{{Type: "text", Value: "c"}}, Attributes: map[string]string{"color": "red"}},
},
}
initialXML := `<root><p>a</p><p>b</p><p>c</p></root>`
initialXML := `<root><p color="red">a</p><p color="red">b</p><p color="red">c</p></root>`

content := &json.TreeNode{Type: "p", Attributes: map[string]string{"italic": "true"}, Children: []json.TreeNode{{Type: "text", Value: `d`}}}

ranges := []twoRangesType{
// equal: <p>b</p> - <p>b</p>
makeTwoRanges(3, 3, 6, 3, -1, 6, `equal`),
// equal multiple: <p>a</p><p>b</p><p>c</p> - <p>a</p><p>b</p><p>c</p>
makeTwoRanges(0, 3, 9, 0, 3, 9, `equal multiple`),
// A contains B: <p>a</p><p>b</p><p>c</p> - <p>b</p>
makeTwoRanges(0, 3, 9, 3, -1, 6, `A contains B`),
// B contains A: <p>b</p> - <p>a</p><p>b</p><p>c</p>
Expand All @@ -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`},
}

Expand Down
4 changes: 2 additions & 2 deletions test/integration/tree_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,7 @@ func TestTree(t *testing.T) {
assert.Equal(t, "<root><p>a</p><p></p><p>b</p></root>", d1.Root().GetTree("t").ToXML())
})

t.Run("contained-split-and-split-at-diffrent-positions-on-the-same-node", func(t *testing.T) {
t.Run("contained-split-and-split-at-different-positions-on-the-same-node", func(t *testing.T) {
ctx := context.Background()
d1 := document.New(helper.TestDocKey(t))
assert.NoError(t, c1.Attach(ctx, d1))
Expand Down Expand Up @@ -2623,7 +2623,7 @@ func TestTree(t *testing.T) {
assert.Equal(t, "<root><p>a</p><p>b</p></root>", d1.Root().GetTree("t").ToXML())
})

t.Run("side-by-side-split-and-delete", func(t *testing.T) {
t.Run("side-by-side-split-and-merge", func(t *testing.T) {
ctx := context.Background()
d1 := document.New(helper.TestDocKey(t))
assert.NoError(t, c1.Attach(ctx, d1))
Expand Down

0 comments on commit 94d6e30

Please sign in to comment.