Commit 64ea8239 authored by Petar Maymounkov's avatar Petar Maymounkov

CheckInvariant returns info instead of panicking.

parent 625914b4
......@@ -16,8 +16,12 @@ func TestMutableAndImmutableAddSame(t *testing.T) {
mut.Add(k)
immut = Add(immut, k)
}
mut.CheckInvariant()
immut.CheckInvariant()
if d := mut.CheckInvariant(); d != nil {
t.Fatalf("mutable trie invariant discrepancy: %v", d)
}
if d := immut.CheckInvariant(); d != nil {
t.Fatalf("immutable trie invariant discrepancy: %v", d)
}
if !Equal(mut, immut) {
t.Errorf("mutable trie %v differs from immutable trie %v", mut, immut)
}
......@@ -29,16 +33,19 @@ func TestAddIsOrderIndependent(t *testing.T) {
base := New()
for _, k := range s.Keys {
base.Add(k)
base.CheckInvariant()
}
base.CheckInvariant()
if d := base.CheckInvariant(); d != nil {
t.Fatalf("base trie invariant discrepancy: %v", d)
}
for j := 0; j < 100; j++ {
perm := rand.Perm(len(s.Keys))
reordered := New()
for i := range s.Keys {
reordered.Add(s.Keys[perm[i]])
}
reordered.CheckInvariant()
if d := reordered.CheckInvariant(); d != nil {
t.Fatalf("reordered trie invariant discrepancy: %v", d)
}
if !Equal(base, reordered) {
t.Errorf("trie %v differs from trie %v", base, reordered)
}
......
......@@ -4,35 +4,70 @@ import (
"github.com/libp2p/go-libp2p-xor/key"
)
type InvariantDiscrepancy struct {
Reason string
PathToDiscrepancy string
KeyAtDiscrepancy string
}
// CheckInvariant panics of the trie does not meet its invariant.
func (trie *Trie) CheckInvariant() {
trie.checkInvariant(0, nil)
func (trie *Trie) CheckInvariant() *InvariantDiscrepancy {
return trie.checkInvariant(0, nil)
}
func (trie *Trie) checkInvariant(depth int, pathSoFar *triePath) {
func (trie *Trie) checkInvariant(depth int, pathSoFar *triePath) *InvariantDiscrepancy {
switch {
case trie.IsEmptyLeaf(): // ok
case trie.IsEmptyLeaf():
return nil
case trie.IsNonEmptyLeaf():
if !pathSoFar.matchesKey(trie.Key) {
panic("key found at invalid location in trie")
return &InvariantDiscrepancy{
Reason: "key found at invalid location in trie",
PathToDiscrepancy: pathSoFar.BitString(),
KeyAtDiscrepancy: trie.Key.BitString(),
}
}
return nil
default:
if trie.IsEmpty() {
b0, b1 := trie.Branch[0], trie.Branch[1]
b0.checkInvariant(depth+1, pathSoFar.Push(0))
b1.checkInvariant(depth+1, pathSoFar.Push(1))
if d0 := b0.checkInvariant(depth+1, pathSoFar.Push(0)); d0 != nil {
return d0
}
if d1 := b1.checkInvariant(depth+1, pathSoFar.Push(1)); d1 != nil {
return d1
}
switch {
case b0.IsEmptyLeaf() && b1.IsEmptyLeaf():
panic("intermediate node with two empty leaves")
return &InvariantDiscrepancy{
Reason: "intermediate node with two empty leaves",
PathToDiscrepancy: pathSoFar.BitString(),
KeyAtDiscrepancy: "none",
}
case b0.IsEmptyLeaf() && b1.IsNonEmptyLeaf():
panic("intermediate node with one empty leaf")
return &InvariantDiscrepancy{
Reason: "intermediate node with one empty leaf/0",
PathToDiscrepancy: pathSoFar.BitString(),
KeyAtDiscrepancy: "none",
}
case b0.IsNonEmptyLeaf() && b1.IsEmptyLeaf():
panic("intermediate node with one empty leaf")
return &InvariantDiscrepancy{
Reason: "intermediate node with one empty leaf/1",
PathToDiscrepancy: pathSoFar.BitString(),
KeyAtDiscrepancy: "none",
}
default:
return nil
}
} else {
panic("intermediate node with a key")
return &InvariantDiscrepancy{
Reason: "intermediate node with a key",
PathToDiscrepancy: pathSoFar.BitString(),
KeyAtDiscrepancy: trie.Key.BitString(),
}
}
}
panic("unreachable")
}
type triePath struct {
......@@ -73,19 +108,19 @@ func (p *triePath) walk(k key.Key, depthToLeaf int) (ok bool, depthToRoot int) {
}
}
func (p *triePath) String() string {
return p.string(0)
func (p *triePath) BitString() string {
return p.bitString(0)
}
func (p *triePath) string(depthToLeaf int) string {
func (p *triePath) bitString(depthToLeaf int) string {
if p == nil {
return ""
} else {
switch {
case p.bit == 0:
return p.parent.string(depthToLeaf+1) + "0"
return p.parent.bitString(depthToLeaf+1) + "0"
case p.bit == 1:
return p.parent.string(depthToLeaf+1) + "1"
return p.parent.bitString(depthToLeaf+1) + "1"
default:
panic("bit digit > 1")
}
......
......@@ -32,16 +32,22 @@ func testIntersect(t *testing.T, sample *testIntersectSample) {
for _, l := range sample.LeftKeys {
left.Add(l)
}
left.CheckInvariant()
if d := left.CheckInvariant(); d != nil {
t.Fatalf("left trie invariant discrepancy: %v", d)
}
for _, r := range sample.RightKeys {
right.Add(r)
}
right.CheckInvariant()
if d := right.CheckInvariant(); d != nil {
t.Fatalf("right trie invariant discrepancy: %v", d)
}
for _, s := range setIntersect(sample.LeftKeys, sample.RightKeys) {
expected.Add(s)
}
got := Intersect(left, right)
got.CheckInvariant()
if d := got.CheckInvariant(); d != nil {
t.Fatalf("right trie invariant discrepancy: %v", d)
}
if !Equal(expected, got) {
t.Errorf("intersection of %v and %v: expected %v, got %v",
sample.LeftKeys, sample.RightKeys, expected, got)
......@@ -140,3 +146,55 @@ var testIntersectJSONSamples = []string{
}
`,
}
func TestIntersectTriesFromJSON(t *testing.T) {
for _, json := range testIntersectJSONTries {
s := testIntersectTrieFromJSON(json)
testIntersectTries(t, s)
}
}
func testIntersectTries(t *testing.T, sample *testIntersectTrie) {
if d := sample.LeftTrie.CheckInvariant(); d != nil {
t.Fatalf("left trie invariant discrepancy: %v", d)
}
if d := sample.RightTrie.CheckInvariant(); d != nil {
t.Fatalf("right trie invariant discrepancy: %v", d)
}
expected := New()
for _, s := range setIntersect(sample.LeftTrie.List(), sample.RightTrie.List()) {
expected.Add(s)
}
got := Intersect(sample.LeftTrie, sample.RightTrie)
if d := got.CheckInvariant(); d != nil {
t.Fatalf("got trie invariant discrepancy: %v", d)
}
if !Equal(expected, got) {
t.Errorf("intersection of %v and %v: expected %v, got %v",
sample.LeftTrie, sample.RightTrie, expected, got)
}
}
type testIntersectTrie struct {
LeftTrie *Trie
RightTrie *Trie
}
func testIntersectTrieFromJSON(srcJSON string) *testIntersectTrie {
s := &testIntersectTrie{}
if err := json.Unmarshal([]byte(srcJSON), s); err != nil {
panic(err)
}
return s
}
var testIntersectJSONTries = []string{
// `
// {
// "LeftTrie": [
// ],
// "RightTrie": [
// ]
// }
// `,
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment