mirror of
https://github.com/matrix-org/sliding-sync.git
synced 2025-03-10 13:37:11 +00:00

- When a range is shrinked from both ends, we would incorrectly send back INVALIDATE twice for the same range (the higher end). This was caused by improper usage of Go for .. range loops - When a range is changed to a window completely outside the room list (e.g 5 rooms, range=[10,15]) we would incorrectly send back SYNC 10,15 with a single room ID, the last room in the list. This was caused by boundary check code in SliceInto capping ranges to the last element if the index positions are > room list. However in this case, this causes the last room to be returned. With regression tests (UT and E2E)
664 lines
13 KiB
Go
664 lines
13 KiB
Go
package sync3
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
type stringSlice []string
|
|
|
|
func (s stringSlice) Len() int64 {
|
|
return int64(len(s))
|
|
}
|
|
func (s stringSlice) Subslice(i, j int64) Subslicer {
|
|
return s[i:j]
|
|
}
|
|
|
|
func TestRangeValid(t *testing.T) {
|
|
testCases := []struct {
|
|
input SliceRanges
|
|
valid bool
|
|
}{
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
valid: true,
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{9, 0},
|
|
}),
|
|
valid: false,
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{9, 9},
|
|
}),
|
|
valid: true,
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{-3, 3},
|
|
}),
|
|
valid: false,
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{0, 20}, {20, 40}, // 20 overlaps
|
|
}),
|
|
valid: false,
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{0, 20}, {40, 60}, {30, 35},
|
|
}),
|
|
valid: true,
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{0, 20}, {40, 60}, {10, 15},
|
|
}),
|
|
valid: false,
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{10, 15}, {40, 60}, {0, 20},
|
|
}),
|
|
valid: false,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
gotValid := tc.input.Valid()
|
|
if gotValid != tc.valid {
|
|
t.Errorf("test case %+v returned valid=%v", tc, gotValid)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRange(t *testing.T) {
|
|
alphabet := []string{
|
|
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
|
|
}
|
|
testCases := []struct {
|
|
input SliceRanges
|
|
want [][]string
|
|
}{
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
want: [][]string{
|
|
alphabet[0:10],
|
|
},
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{0, 99},
|
|
}),
|
|
want: [][]string{
|
|
alphabet,
|
|
},
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{0, 0}, {1, 1},
|
|
}),
|
|
want: [][]string{
|
|
alphabet[0:1], alphabet[1:2],
|
|
},
|
|
},
|
|
{
|
|
input: SliceRanges([][2]int64{
|
|
{30, 99},
|
|
}),
|
|
want: [][]string{},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
result := tc.input.SliceInto(stringSlice(alphabet))
|
|
if len(result) != len(tc.want) {
|
|
t.Errorf("%+v subslice mismatch: got %v want %v \ngot: %+v\nwant: %+v\n", tc, len(result), len(tc.want), result, tc.want)
|
|
continue
|
|
}
|
|
for i := range result {
|
|
got := result[i].(stringSlice)
|
|
want := tc.want[i]
|
|
if !reflect.DeepEqual([]string(got), want) {
|
|
t.Errorf("%+v wrong subslice returned, got %v want %v", tc, got, want)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
func TestRangeInside(t *testing.T) {
|
|
testCases := []struct {
|
|
testRange SliceRanges
|
|
i int64
|
|
inside bool
|
|
}{
|
|
{
|
|
testRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
i: 6,
|
|
inside: true,
|
|
},
|
|
{
|
|
testRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
i: 0,
|
|
inside: true,
|
|
},
|
|
{
|
|
testRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
i: 9,
|
|
inside: true,
|
|
},
|
|
{
|
|
testRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
i: -1,
|
|
inside: false,
|
|
},
|
|
{
|
|
testRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
i: 10,
|
|
inside: false,
|
|
},
|
|
{
|
|
testRange: SliceRanges([][2]int64{
|
|
{0, 9}, {10, 19},
|
|
}),
|
|
i: 10,
|
|
inside: true,
|
|
},
|
|
{
|
|
testRange: SliceRanges([][2]int64{
|
|
{0, 9}, {20, 29},
|
|
}),
|
|
i: 10,
|
|
inside: false,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
_, gotInside := tc.testRange.Inside(tc.i)
|
|
if gotInside != tc.inside {
|
|
t.Errorf("%+v got Inside:%v want %v", tc, gotInside, tc.inside)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRangeClosestInDirection(t *testing.T) {
|
|
testCases := []struct {
|
|
ranges SliceRanges
|
|
i int64
|
|
towardsZero bool
|
|
wantClosestIndex int64
|
|
}{
|
|
{
|
|
ranges: [][2]int64{{0, 20}},
|
|
i: 15,
|
|
towardsZero: true,
|
|
wantClosestIndex: 0,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 20}},
|
|
i: 0,
|
|
towardsZero: false,
|
|
wantClosestIndex: 20,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 20}},
|
|
i: 20,
|
|
towardsZero: true,
|
|
wantClosestIndex: 0,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 20}},
|
|
i: 0,
|
|
towardsZero: true,
|
|
wantClosestIndex: 0,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 20}},
|
|
i: 20,
|
|
towardsZero: false,
|
|
wantClosestIndex: 20,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 20}},
|
|
i: 15,
|
|
towardsZero: false,
|
|
wantClosestIndex: 20,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}},
|
|
i: 5,
|
|
towardsZero: true,
|
|
wantClosestIndex: -1,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}},
|
|
i: 5,
|
|
towardsZero: false,
|
|
wantClosestIndex: 10,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}},
|
|
i: 25,
|
|
towardsZero: false,
|
|
wantClosestIndex: -1,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}},
|
|
i: 25,
|
|
towardsZero: true,
|
|
wantClosestIndex: 20,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}, {30, 40}},
|
|
i: 25,
|
|
towardsZero: true,
|
|
wantClosestIndex: 20,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}, {30, 40}},
|
|
i: 25,
|
|
towardsZero: false,
|
|
wantClosestIndex: 30,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}, {30, 40}},
|
|
i: 35,
|
|
towardsZero: true,
|
|
wantClosestIndex: 30,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 20}, {30, 40}},
|
|
i: 35,
|
|
towardsZero: false,
|
|
wantClosestIndex: 40,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 0}},
|
|
i: 0,
|
|
towardsZero: true,
|
|
wantClosestIndex: 0,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 0}},
|
|
i: 0,
|
|
towardsZero: false,
|
|
wantClosestIndex: 0,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 10}},
|
|
i: 10,
|
|
towardsZero: false,
|
|
wantClosestIndex: 10,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{10, 10}},
|
|
i: 10,
|
|
towardsZero: true,
|
|
wantClosestIndex: 10,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{20, 40}},
|
|
i: 40,
|
|
towardsZero: true,
|
|
wantClosestIndex: 20,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 20}, {40, 60}},
|
|
i: 40,
|
|
towardsZero: true,
|
|
wantClosestIndex: 40,
|
|
},
|
|
{
|
|
ranges: [][2]int64{{0, 20}, {40, 60}},
|
|
i: 20,
|
|
towardsZero: false,
|
|
wantClosestIndex: 20,
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
gotClosest := tc.ranges.ClosestInDirection(tc.i, tc.towardsZero)
|
|
if gotClosest != tc.wantClosestIndex {
|
|
t.Errorf("%+v: got %v want %v", tc, gotClosest, tc.wantClosestIndex)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRangeDelta(t *testing.T) {
|
|
testCases := []struct {
|
|
oldRange SliceRanges
|
|
newRange SliceRanges
|
|
wantAdded SliceRanges
|
|
wantSames SliceRanges
|
|
wantRemoved SliceRanges
|
|
}{
|
|
// added
|
|
{
|
|
oldRange: SliceRanges([][2]int64{}),
|
|
newRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
wantAdded: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
},
|
|
// removed
|
|
{
|
|
oldRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
newRange: SliceRanges([][2]int64{}),
|
|
wantRemoved: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
},
|
|
// same
|
|
{
|
|
oldRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
newRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
wantSames: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
},
|
|
// typical range increase
|
|
{
|
|
oldRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
newRange: SliceRanges([][2]int64{
|
|
{0, 9}, {10, 19},
|
|
}),
|
|
wantSames: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
wantAdded: SliceRanges([][2]int64{
|
|
{10, 19},
|
|
}),
|
|
},
|
|
// typical range swap
|
|
{
|
|
oldRange: SliceRanges([][2]int64{
|
|
{0, 9}, {10, 19},
|
|
}),
|
|
newRange: SliceRanges([][2]int64{
|
|
{0, 9}, {20, 29},
|
|
}),
|
|
wantSames: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
wantAdded: SliceRanges([][2]int64{
|
|
{20, 29},
|
|
}),
|
|
wantRemoved: SliceRanges([][2]int64{
|
|
{10, 19},
|
|
}),
|
|
},
|
|
// overlaps are handled intelligently
|
|
{
|
|
oldRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
newRange: SliceRanges([][2]int64{
|
|
{0, 10},
|
|
}),
|
|
wantSames: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
wantAdded: SliceRanges([][2]int64{
|
|
{10, 10},
|
|
}),
|
|
wantRemoved: [][2]int64{},
|
|
},
|
|
{
|
|
oldRange: SliceRanges([][2]int64{
|
|
{0, 9},
|
|
}),
|
|
newRange: SliceRanges([][2]int64{
|
|
{5, 15},
|
|
}),
|
|
wantSames: SliceRanges([][2]int64{
|
|
{5, 9},
|
|
}),
|
|
wantAdded: SliceRanges([][2]int64{
|
|
{10, 15},
|
|
}),
|
|
wantRemoved: SliceRanges([][2]int64{
|
|
{0, 4},
|
|
}),
|
|
},
|
|
{
|
|
oldRange: [][2]int64{{5, 15}},
|
|
newRange: [][2]int64{{0, 9}},
|
|
wantSames: [][2]int64{{5, 9}},
|
|
wantAdded: [][2]int64{{0, 4}},
|
|
wantRemoved: [][2]int64{{10, 15}},
|
|
},
|
|
{
|
|
oldRange: [][2]int64{{0, 20}, {35, 45}},
|
|
newRange: [][2]int64{{0, 20}, {36, 46}},
|
|
wantSames: [][2]int64{{0, 20}, {36, 45}},
|
|
wantAdded: [][2]int64{{46, 46}},
|
|
wantRemoved: [][2]int64{{35, 35}},
|
|
},
|
|
{
|
|
oldRange: [][2]int64{{0, 20}, {35, 45}},
|
|
newRange: [][2]int64{{0, 20}, {34, 44}},
|
|
wantSames: [][2]int64{{0, 20}, {35, 44}},
|
|
wantAdded: [][2]int64{{34, 34}},
|
|
wantRemoved: [][2]int64{{45, 45}},
|
|
},
|
|
// torture window, multiple same, add, remove
|
|
{
|
|
oldRange: [][2]int64{{10, 20}, {40, 50}, {70, 80}, {100, 110}},
|
|
newRange: [][2]int64{{0, 15}, {40, 50}, {80, 90}, {111, 120}},
|
|
wantSames: [][2]int64{{10, 15}, {40, 50}, {80, 80}},
|
|
wantAdded: [][2]int64{{0, 9}, {81, 90}, {111, 120}},
|
|
wantRemoved: [][2]int64{{16, 20}, {70, 79}, {100, 110}},
|
|
},
|
|
// swallowed range (one getting smaller, one getting bigger)
|
|
{
|
|
oldRange: [][2]int64{{0, 20}},
|
|
newRange: [][2]int64{{5, 15}},
|
|
wantSames: [][2]int64{{5, 15}},
|
|
wantRemoved: [][2]int64{{0, 4}, {16, 20}},
|
|
},
|
|
{
|
|
oldRange: [][2]int64{{5, 15}},
|
|
newRange: [][2]int64{{0, 20}},
|
|
wantSames: [][2]int64{{5, 15}},
|
|
wantAdded: [][2]int64{{0, 4}, {16, 20}},
|
|
},
|
|
// regression test
|
|
{
|
|
oldRange: [][2]int64{{0, 20}, {20, 24}},
|
|
newRange: [][2]int64{{0, 20}, {20, 24}},
|
|
wantSames: [][2]int64{{0, 20}, {20, 24}},
|
|
wantAdded: [][2]int64{},
|
|
wantRemoved: [][2]int64{},
|
|
},
|
|
// another regression test
|
|
{
|
|
oldRange: [][2]int64{{0, 0}},
|
|
newRange: [][2]int64{{0, 0}},
|
|
wantSames: [][2]int64{{0, 0}},
|
|
wantAdded: [][2]int64{},
|
|
wantRemoved: [][2]int64{},
|
|
},
|
|
// regression test, 1 element overlap
|
|
{
|
|
oldRange: [][2]int64{{10, 20}},
|
|
newRange: [][2]int64{{20, 30}},
|
|
wantSames: [][2]int64{{20, 20}},
|
|
wantAdded: [][2]int64{{21, 30}},
|
|
wantRemoved: [][2]int64{{10, 19}},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
gotAdd, gotRm, gotSame := tc.oldRange.Delta(tc.newRange)
|
|
if tc.wantAdded != nil && !reflect.DeepEqual(gotAdd, tc.wantAdded) {
|
|
t.Errorf("%v -> %v got added %+v want %+v", tc.oldRange, tc.newRange, gotAdd, tc.wantAdded)
|
|
}
|
|
if tc.wantRemoved != nil && !reflect.DeepEqual(gotRm, tc.wantRemoved) {
|
|
t.Errorf("%v -> %v got remove %+v want %+v", tc.oldRange, tc.newRange, gotRm, tc.wantRemoved)
|
|
}
|
|
if tc.wantSames != nil && !reflect.DeepEqual(gotSame, tc.wantSames) {
|
|
t.Errorf("%v -> %v got same %+v want %+v", tc.oldRange, tc.newRange, gotSame, tc.wantSames)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSortPoints(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
input []pointInfo
|
|
want []pointInfo
|
|
}{
|
|
{
|
|
name: "two element",
|
|
input: []pointInfo{
|
|
{
|
|
x: 5,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 2,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
},
|
|
want: []pointInfo{
|
|
{
|
|
x: 2,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 5,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "no dupes, sort by x",
|
|
input: []pointInfo{
|
|
{
|
|
x: 4,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 1,
|
|
isOldRange: true,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
x: 3,
|
|
isOldRange: false,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 2,
|
|
isOldRange: false,
|
|
isOpen: false,
|
|
},
|
|
},
|
|
want: []pointInfo{
|
|
{
|
|
x: 1,
|
|
isOldRange: true,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
x: 2,
|
|
isOldRange: false,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
x: 3,
|
|
isOldRange: false,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 4,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "all dupes, sort by open(tie=old), close(tie=old)",
|
|
input: []pointInfo{
|
|
{
|
|
x: 4,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 4,
|
|
isOldRange: false,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 4,
|
|
isOldRange: false,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
x: 4,
|
|
isOldRange: true,
|
|
isOpen: false,
|
|
},
|
|
},
|
|
want: []pointInfo{
|
|
{
|
|
x: 4,
|
|
isOldRange: true,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 4,
|
|
isOldRange: false,
|
|
isOpen: true,
|
|
},
|
|
{
|
|
x: 4,
|
|
isOldRange: true,
|
|
isOpen: false,
|
|
},
|
|
{
|
|
x: 4,
|
|
isOldRange: false,
|
|
isOpen: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
sortPoints(tc.input)
|
|
if !reflect.DeepEqual(tc.input, tc.want) {
|
|
t.Errorf("%s: got %+v\nwant %+v", tc.name, tc.input, tc.want)
|
|
}
|
|
}
|
|
}
|