syncthing/lib/protocol/vector_test.go
Jakob Borg 744ef0d8ac
lib/protocol: Avoid data loss on database wipe by higher version numbers (fixes #3876) (#6605)
This makes version vector values clock based instead of just incremented
from zero. The effect is that a vector that is created from scratch
(after database reset) will have a higher value for the local device
than what it could have been previously, causing a conflict. That is, if
we are A and we had

    {A: 42, B: 12}

in the old scheme, a reset and rescan would give us

    {A: 1}

which is a strict ancestor of the older file (this might be wrong). With
the new scheme we would instead have

    {A: someClockTime, b: otherClockTime}

and the new version after reset would become

    {A: someClockTime+delta}

which is in conflict with the previous entry (better).
In case the clocks are wrong (current time is less than the value in the
vector) we fall back to just simple increment like today.

This scheme is ineffective if we suffer a database reset while at the
same time setting the clock back far into the past. It's however no
worse than what we already do.

This loses the ability to emit the "added" event, as we can't look for
the magic 1 entry any more. That event was however already broken
(#5541).

Another place where we infer meaning from the vector itself is in
receive only folders, but there the only criteria is that the vector is
one item long and includes just ourselves, which remains the case with
this change.

* wip
2020-05-06 08:47:02 +02:00

387 lines
11 KiB
Go

// Copyright (C) 2015 The Protocol Authors.
package protocol
import (
"math"
"testing"
)
func TestUpdate(t *testing.T) {
var v Vector
// Append
v = v.updateWithNow(42, 5)
expected := Vector{Counters: []Counter{{ID: 42, Value: 5}}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Insert at front
v = v.updateWithNow(36, 6)
expected = Vector{Counters: []Counter{{ID: 36, Value: 6}, {ID: 42, Value: 5}}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Insert in middle
v = v.updateWithNow(37, 7)
expected = Vector{Counters: []Counter{{ID: 36, Value: 6}, {ID: 37, Value: 7}, {ID: 42, Value: 5}}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Update existing
v = v.updateWithNow(37, 1)
expected = Vector{Counters: []Counter{{ID: 36, Value: 6}, {ID: 37, Value: 8}, {ID: 42, Value: 5}}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Update existing with higher current time
v = v.updateWithNow(37, 100)
expected = Vector{Counters: []Counter{{ID: 36, Value: 6}, {ID: 37, Value: 100}, {ID: 42, Value: 5}}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
// Update existing with lower current time
v = v.updateWithNow(37, 50)
expected = Vector{Counters: []Counter{{ID: 36, Value: 6}, {ID: 37, Value: 101}, {ID: 42, Value: 5}}}
if v.Compare(expected) != Equal {
t.Errorf("Update error, %+v != %+v", v, expected)
}
}
func TestCopy(t *testing.T) {
v0 := Vector{Counters: []Counter{{ID: 42, Value: 1}}}
v1 := v0.Copy()
v1.Update(42)
if v0.Compare(v1) != Lesser {
t.Errorf("Copy error, %+v should be ancestor of %+v", v0, v1)
}
}
func TestMerge(t *testing.T) {
testcases := []struct {
a, b, m Vector
}{
// No-ops
{
Vector{},
Vector{},
Vector{},
},
{
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
},
// Appends
{
Vector{},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
},
{
Vector{Counters: []Counter{{ID: 22, Value: 1}}},
Vector{Counters: []Counter{{ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
},
{
Vector{Counters: []Counter{{ID: 22, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
},
// Insert
{
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 23, Value: 2}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 23, Value: 2}, {ID: 42, Value: 1}}},
},
{
Vector{Counters: []Counter{{ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 1}}},
},
// Update
{
Vector{Counters: []Counter{{ID: 22, Value: 1}, {ID: 42, Value: 2}}},
Vector{Counters: []Counter{{ID: 22, Value: 2}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 2}, {ID: 42, Value: 2}}},
},
// All of the above
{
Vector{Counters: []Counter{{ID: 10, Value: 1}, {ID: 20, Value: 2}, {ID: 30, Value: 1}}},
Vector{Counters: []Counter{{ID: 5, Value: 1}, {ID: 10, Value: 2}, {ID: 15, Value: 1}, {ID: 20, Value: 1}, {ID: 25, Value: 1}, {ID: 35, Value: 1}}},
Vector{Counters: []Counter{{ID: 5, Value: 1}, {ID: 10, Value: 2}, {ID: 15, Value: 1}, {ID: 20, Value: 2}, {ID: 25, Value: 1}, {ID: 30, Value: 1}, {ID: 35, Value: 1}}},
},
}
for i, tc := range testcases {
if m := tc.a.Merge(tc.b); m.Compare(tc.m) != Equal {
t.Errorf("%d: %+v.Merge(%+v) == %+v (expected %+v)", i, tc.a, tc.b, m, tc.m)
}
}
}
func TestCounterValue(t *testing.T) {
v0 := Vector{Counters: []Counter{{ID: 42, Value: 1}, {ID: 64, Value: 5}}}
if v0.Counter(42) != 1 {
t.Errorf("Counter error, %d != %d", v0.Counter(42), 1)
}
if v0.Counter(64) != 5 {
t.Errorf("Counter error, %d != %d", v0.Counter(64), 5)
}
if v0.Counter(72) != 0 {
t.Errorf("Counter error, %d != %d", v0.Counter(72), 0)
}
}
func TestCompare(t *testing.T) {
testcases := []struct {
a, b Vector
r Ordering
}{
// Empty vectors are identical
{Vector{}, Vector{}, Equal},
{Vector{}, Vector{Counters: []Counter{{ID: 42, Value: 0}}}, Equal},
{Vector{Counters: []Counter{{ID: 42, Value: 0}}}, Vector{}, Equal},
// Zero is the implied value for a missing Counter
{
Vector{Counters: []Counter{{ID: 42, Value: 0}}},
Vector{Counters: []Counter{{ID: 77, Value: 0}}},
Equal,
},
// Equal vectors are equal
{
Vector{Counters: []Counter{{ID: 42, Value: 33}}},
Vector{Counters: []Counter{{ID: 42, Value: 33}}},
Equal,
},
{
Vector{Counters: []Counter{{ID: 42, Value: 33}, {ID: 77, Value: 24}}},
Vector{Counters: []Counter{{ID: 42, Value: 33}, {ID: 77, Value: 24}}},
Equal,
},
// These a-vectors are all greater than the b-vector
{
Vector{Counters: []Counter{{ID: 42, Value: 1}}},
Vector{},
Greater,
},
{
Vector{Counters: []Counter{{ID: 0, Value: 1}}},
Vector{Counters: []Counter{{ID: 0, Value: 0}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 42, Value: 0}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: math.MaxUint64, Value: 1}}},
Vector{Counters: []Counter{{ID: math.MaxUint64, Value: 0}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 0, Value: math.MaxUint64}}},
Vector{Counters: []Counter{{ID: 0, Value: 0}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 42, Value: math.MaxUint64}}},
Vector{Counters: []Counter{{ID: 42, Value: 0}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: math.MaxUint64, Value: math.MaxUint64}}},
Vector{Counters: []Counter{{ID: math.MaxUint64, Value: 0}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 0, Value: math.MaxUint64}}},
Vector{Counters: []Counter{{ID: 0, Value: math.MaxUint64 - 1}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 42, Value: math.MaxUint64}}},
Vector{Counters: []Counter{{ID: 42, Value: math.MaxUint64 - 1}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: math.MaxUint64, Value: math.MaxUint64}}},
Vector{Counters: []Counter{{ID: math.MaxUint64, Value: math.MaxUint64 - 1}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 42, Value: 2}}},
Vector{Counters: []Counter{{ID: 42, Value: 1}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 2}}},
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 1}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 42, Value: 2}, {ID: 77, Value: 3}}},
Vector{Counters: []Counter{{ID: 42, Value: 1}, {ID: 77, Value: 3}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 2}, {ID: 77, Value: 3}}},
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 1}, {ID: 77, Value: 3}}},
Greater,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 23}, {ID: 42, Value: 2}, {ID: 77, Value: 4}}},
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 1}, {ID: 77, Value: 3}}},
Greater,
},
// These a-vectors are all lesser than the b-vector
{Vector{}, Vector{Counters: []Counter{{ID: 42, Value: 1}}}, Lesser},
{
Vector{Counters: []Counter{{ID: 42, Value: 0}}},
Vector{Counters: []Counter{{ID: 42, Value: 1}}},
Lesser,
},
{
Vector{Counters: []Counter{{ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 42, Value: 2}}},
Lesser,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 2}}},
Lesser,
},
{
Vector{Counters: []Counter{{ID: 42, Value: 1}, {ID: 77, Value: 3}}},
Vector{Counters: []Counter{{ID: 42, Value: 2}, {ID: 77, Value: 3}}},
Lesser,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 1}, {ID: 77, Value: 3}}},
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 2}, {ID: 77, Value: 3}}},
Lesser,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 1}, {ID: 77, Value: 3}}},
Vector{Counters: []Counter{{ID: 22, Value: 23}, {ID: 42, Value: 2}, {ID: 77, Value: 4}}},
Lesser,
},
// These are all in conflict
{
Vector{Counters: []Counter{{ID: 42, Value: 2}}},
Vector{Counters: []Counter{{ID: 43, Value: 1}}},
ConcurrentGreater,
},
{
Vector{Counters: []Counter{{ID: 43, Value: 1}}},
Vector{Counters: []Counter{{ID: 42, Value: 2}}},
ConcurrentLesser,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 23}, {ID: 42, Value: 1}}},
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 2}}},
ConcurrentGreater,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 21}, {ID: 42, Value: 2}}},
Vector{Counters: []Counter{{ID: 22, Value: 22}, {ID: 42, Value: 1}}},
ConcurrentLesser,
},
{
Vector{Counters: []Counter{{ID: 22, Value: 21}, {ID: 42, Value: 2}, {ID: 43, Value: 1}}},
Vector{Counters: []Counter{{ID: 20, Value: 1}, {ID: 22, Value: 22}, {ID: 42, Value: 1}}},
ConcurrentLesser,
},
}
for i, tc := range testcases {
// Test real Compare
if r := tc.a.Compare(tc.b); r != tc.r {
t.Errorf("%d: %+v.Compare(%+v) == %v (expected %v)", i, tc.a, tc.b, r, tc.r)
}
// Test convenience functions
switch tc.r {
case Greater:
if tc.a.Equal(tc.b) {
t.Errorf("%+v == %+v", tc.a, tc.b)
}
if tc.a.Concurrent(tc.b) {
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
}
if !tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v not >= %+v", tc.a, tc.b)
}
if tc.a.LesserEqual(tc.b) {
t.Errorf("%+v <= %+v", tc.a, tc.b)
}
case Lesser:
if tc.a.Concurrent(tc.b) {
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
}
if tc.a.Equal(tc.b) {
t.Errorf("%+v == %+v", tc.a, tc.b)
}
if tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v >= %+v", tc.a, tc.b)
}
if !tc.a.LesserEqual(tc.b) {
t.Errorf("%+v not <= %+v", tc.a, tc.b)
}
case Equal:
if tc.a.Concurrent(tc.b) {
t.Errorf("%+v concurrent %+v", tc.a, tc.b)
}
if !tc.a.Equal(tc.b) {
t.Errorf("%+v not == %+v", tc.a, tc.b)
}
if !tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v not <= %+v", tc.a, tc.b)
}
if !tc.a.LesserEqual(tc.b) {
t.Errorf("%+v not <= %+v", tc.a, tc.b)
}
case ConcurrentLesser, ConcurrentGreater:
if !tc.a.Concurrent(tc.b) {
t.Errorf("%+v not concurrent %+v", tc.a, tc.b)
}
if tc.a.Equal(tc.b) {
t.Errorf("%+v == %+v", tc.a, tc.b)
}
if tc.a.GreaterEqual(tc.b) {
t.Errorf("%+v >= %+v", tc.a, tc.b)
}
if tc.a.LesserEqual(tc.b) {
t.Errorf("%+v <= %+v", tc.a, tc.b)
}
}
}
}