package negentropy import ( "bytes" "testing" ) func makeItem(ts int64, id byte) Item { b := []byte{:32} b[0] = id return Item{Timestamp: ts, ID: b} } func TestFingerprintIdentical(t *testing.T) { items := []Item{makeItem(1, 0xaa), makeItem(2, 0xbb)} f1 := Fingerprint(items) f2 := Fingerprint(items) if f1 != f2 { t.Error("identical items should produce identical fingerprints") } } func TestFingerprintDiffers(t *testing.T) { a := []Item{makeItem(1, 0xaa)} b := []Item{makeItem(1, 0xbb)} if Fingerprint(a) == Fingerprint(b) { t.Error("different items should produce different fingerprints") } } func TestDiffIdentical(t *testing.T) { items := []Item{makeItem(1, 1), makeItem(2, 2)} have, need := Diff(items, items) if len(have) != 0 || len(need) != 0 { t.Errorf("identical sets should have no diff, got have=%d need=%d", len(have), len(need)) } } func TestDiffMissing(t *testing.T) { local := []Item{makeItem(1, 1), makeItem(2, 2)} remote := []Item{makeItem(1, 1), makeItem(2, 2), makeItem(3, 3)} have, need := Diff(local, remote) if len(have) != 0 { t.Errorf("expected 0 have, got %d", len(have)) } if len(need) != 1 || need[0].ID[0] != 3 { t.Errorf("expected need=[item3], got %v", need) } } func TestDiffExtra(t *testing.T) { local := []Item{makeItem(1, 1), makeItem(2, 2), makeItem(3, 3)} remote := []Item{makeItem(1, 1), makeItem(3, 3)} have, need := Diff(local, remote) if len(have) != 1 || have[0].ID[0] != 2 { t.Errorf("expected have=[item2], got %v", have) } if len(need) != 0 { t.Errorf("expected 0 need, got %d", len(need)) } } func TestSplit(t *testing.T) { items := []Item{ makeItem(1, 1), makeItem(2, 2), makeItem(3, 3), makeItem(4, 4), } r := NewReconciler(items) ranges := r.Split(2) if len(ranges) != 2 { t.Fatalf("expected 2 ranges, got %d", len(ranges)) } if ranges[0].Count != 2 || ranges[1].Count != 2 { t.Errorf("expected counts [2,2], got [%d,%d]", ranges[0].Count, ranges[1].Count) } } func TestFindMismatches(t *testing.T) { items := []Item{makeItem(1, 1), makeItem(2, 2), makeItem(3, 3), makeItem(4, 4)} r1 := NewReconciler(items) local := r1.Split(2) // Same items = no mismatches r2 := NewReconciler(items) remote := r2.Split(2) if mm := FindMismatches(local, remote); len(mm) != 0 { t.Errorf("expected 0 mismatches, got %d", len(mm)) } // Different items in second range items2 := []Item{makeItem(1, 1), makeItem(2, 2), makeItem(3, 3), makeItem(4, 0xff)} r3 := NewReconciler(items2) remote2 := r3.Split(2) mm := FindMismatches(local, remote2) if len(mm) != 1 || mm[0] != 1 { t.Errorf("expected mismatch at index 1, got %v", mm) } } func TestItemsFromEvents(t *testing.T) { // Verify sorting a := makeItem(2, 0xbb) b := makeItem(1, 0xaa) items := []Item{a, b} sortItems(items) if items[0].Timestamp != 1 { t.Error("items should be sorted by timestamp") } } func TestEstimateRanges(t *testing.T) { if EstimateRanges(0) != 0 { t.Error("0 items should give 0 ranges") } if EstimateRanges(1) != 2 { t.Error("1 item should give min 2 ranges") } if r := EstimateRanges(100); r != 10 { t.Errorf("100 items: expected 10 ranges, got %d", r) } if r := EstimateRanges(100000); r != 128 { t.Errorf("100000 items: expected 128 max, got %d", r) } } func TestCompareItems(t *testing.T) { a := makeItem(1, 0xaa) b := makeItem(1, 0xbb) c := makeItem(2, 0xaa) if compareItems(a, a) != 0 { t.Error("equal items should compare as 0") } if compareItems(a, b) >= 0 { t.Error("a < b by ID") } if compareItems(a, c) >= 0 { t.Error("a < c by timestamp") } } func TestFingerprintXOR(t *testing.T) { // XOR property: fp(a) XOR fp(b) = fp(a,b) only if no overlap a := makeItem(1, 0xff) b := makeItem(2, 0xff) fpA := Fingerprint([]Item{a}) fpB := Fingerprint([]Item{b}) fpAB := Fingerprint([]Item{a, b}) // XOR of two identical first bytes should be 0 var expected [32]byte for j := 0; j < 32; j++ { expected[j] = fpA[j] ^ fpB[j] } if !bytes.Equal(fpAB[:], expected[:]) { t.Error("fingerprint should be XOR of individual fingerprints") } }