// Copyright 2019 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package compact import ( "fmt" "strings" "testing" ) var factory = &RangeFactory{Hash: func(_, _ []byte) []byte { return []byte("fake-hash") }} func TestAppendRangeErrors(t *testing.T) { anotherFactory := &RangeFactory{Hash: factory.Hash} nonEmpty1, _ := factory.NewRange(7, 8, [][]byte{[]byte("hash")}) nonEmpty2, _ := factory.NewRange(0, 6, [][]byte{[]byte("hash0"), []byte("hash1")}) nonEmpty3, _ := factory.NewRange(6, 7, [][]byte{[]byte("hash")}) corrupt := func(rng *Range, dBegin, dEnd int64) *Range { rng.begin = uint64(int64(rng.begin) + dBegin) rng.end = uint64(int64(rng.end) + dEnd) return rng } for _, tc := range []struct { desc string l, r *Range wantErr string }{ { desc: "ok", l: factory.NewEmptyRange(0), r: factory.NewEmptyRange(0), }, { desc: "incompatible", l: factory.NewEmptyRange(0), r: anotherFactory.NewEmptyRange(0), wantErr: "incompatible ranges", }, { desc: "disjoint", l: factory.NewEmptyRange(0), r: factory.NewEmptyRange(1), wantErr: "ranges are disjoint", }, { desc: "left_corrupted", l: corrupt(factory.NewEmptyRange(7), -7, 0), r: nonEmpty1, wantErr: "corrupted lhs range", }, { desc: "right_corrupted", l: nonEmpty2, r: corrupt(nonEmpty3, 0, 20), wantErr: "corrupted rhs range", }, } { t.Run(tc.desc, func(t *testing.T) { err := tc.l.AppendRange(tc.r, nil) if tc.wantErr == "" { if err != nil { t.Fatalf("AppendRange: %v; want nil", err) } } else if err == nil || !strings.HasPrefix(err.Error(), tc.wantErr) { t.Fatalf("AppendRange: %v; want containing %q", err, tc.wantErr) } }) } } func TestEqual(t *testing.T) { for _, test := range []struct { desc string lhs *Range rhs *Range wantEqual bool }{ { desc: "incompatible trees", lhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, rhs: &Range{ f: &RangeFactory{Hash: factory.Hash}, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, }, { desc: "unequal begin", lhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, rhs: &Range{ f: factory, begin: 18, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, }, { desc: "unequal end", lhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, rhs: &Range{ f: factory, begin: 17, end: 24, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, }, { desc: "unequal number of hashes", lhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, rhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1")}, }, }, { desc: "mismatched hash", lhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, rhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("not hash 2")}, }, }, { desc: "equal ranges", lhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, rhs: &Range{ f: factory, begin: 17, end: 23, hashes: [][]byte{[]byte("hash 1"), []byte("hash 2")}, }, wantEqual: true, }, } { t.Run(test.desc, func(t *testing.T) { if got, want := test.lhs.Equal(test.rhs), test.wantEqual; got != want { t.Errorf("%+v.Equal(%+v) = %v, want %v", test.lhs, test.rhs, got, want) } }) } } func TestGetMergePath(t *testing.T) { for _, tc := range []struct { begin, mid, end uint64 wantLow uint wantHigh uint wantEmpty bool }{ {begin: 0, mid: 0, end: 0, wantEmpty: true}, {begin: 0, mid: 0, end: 1, wantEmpty: true}, {begin: 0, mid: 0, end: uint64(1) << 63, wantEmpty: true}, {begin: 0, mid: 1, end: 1, wantEmpty: true}, {begin: 0, mid: 1, end: 2, wantLow: 0, wantHigh: 1}, {begin: 0, mid: 16, end: 32, wantLow: 4, wantHigh: 5}, {begin: 0, mid: uint64(1) << 63, end: ^uint64(0), wantEmpty: true}, {begin: 0, mid: uint64(1) << 63, end: uint64(1)<<63 + 100500, wantEmpty: true}, {begin: 2, mid: 9, end: 13, wantLow: 0, wantHigh: 2}, {begin: 6, mid: 13, end: 17, wantLow: 0, wantHigh: 3}, {begin: 4, mid: 8, end: 16, wantEmpty: true}, {begin: 8, mid: 12, end: 16, wantLow: 2, wantHigh: 3}, {begin: 4, mid: 6, end: 12, wantLow: 1, wantHigh: 2}, {begin: 8, mid: 10, end: 16, wantLow: 1, wantHigh: 3}, {begin: 11, mid: 17, end: 27, wantLow: 0, wantHigh: 3}, {begin: 11, mid: 16, end: 27, wantEmpty: true}, } { t.Run(fmt.Sprintf("%d:%d:%d", tc.begin, tc.mid, tc.end), func(t *testing.T) { low, high := getMergePath(tc.begin, tc.mid, tc.end) if tc.wantEmpty { if low < high { t.Fatalf("getMergePath(%d,%d,%d)=%d,%d; want empty", tc.begin, tc.mid, tc.end, low, high) } } else if low != tc.wantLow || high != tc.wantHigh { t.Fatalf("getMergePath(%d,%d,%d)=%d,%d; want %d,%d", tc.begin, tc.mid, tc.end, low, high, tc.wantLow, tc.wantHigh) } }) } }