// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package intsets_test import ( "fmt" "log" "math/rand" "sort" "strings" "testing" "code.google.com/p/go.tools/container/intsets" ) func TestBasics(t *testing.T) { var s intsets.Sparse if len := s.Len(); len != 0 { t.Errorf("Len({}): got %d, want 0", len) } if s := s.String(); s != "{}" { t.Errorf("String({}): got %q, want \"{}\"", s) } if s.Has(3) { t.Errorf("Has(3): got true, want false") } if err := s.Check(); err != nil { t.Error(err) } if !s.Insert(3) { t.Errorf("Insert(3): got false, want true") } if max := s.Max(); max != 3 { t.Errorf("Max: got %d, want 3", max) } if !s.Insert(435) { t.Errorf("Insert(435): got false, want true") } if s := s.String(); s != "{3 435}" { t.Errorf("String({3 435}): got %q, want \"{3 435}\"", s) } if max := s.Max(); max != 435 { t.Errorf("Max: got %d, want 435", max) } if len := s.Len(); len != 2 { t.Errorf("Len: got %d, want 2", len) } if !s.Remove(435) { t.Errorf("Remove(435): got false, want true") } if s := s.String(); s != "{3}" { t.Errorf("String({3}): got %q, want \"{3}\"", s) } } // Insert, Len, IsEmpty, Hash, Clear, AppendTo. func TestMoreBasics(t *testing.T) { var set intsets.Sparse set.Insert(456) set.Insert(123) set.Insert(789) if set.Len() != 3 { t.Errorf("%s.Len: got %d, want 3", set, set.Len()) } if set.IsEmpty() { t.Errorf("%s.IsEmpty: got true", set) } if !set.Has(123) { t.Errorf("%s.Has(123): got false", set) } if set.Has(1234) { t.Errorf("%s.Has(1234): got true", set) } got := set.AppendTo([]int{-1}) if want := []int{-1, 123, 456, 789}; fmt.Sprint(got) != fmt.Sprint(want) { t.Errorf("%s.AppendTo: got %v, want %v", got, want) } set.Clear() if set.Len() != 0 { t.Errorf("Clear: got %d, want 0", set.Len()) } if !set.IsEmpty() { t.Errorf("IsEmpty: got false") } if set.Has(123) { t.Errorf("%s.Has: got false", set) } } func TestTakeMin(t *testing.T) { var set intsets.Sparse set.Insert(456) set.Insert(123) set.Insert(789) set.Insert(-123) var got int for i, want := range []int{-123, 123, 456, 789} { if !set.TakeMin(&got) || got != want { t.Errorf("TakeMin #%d: got %d, want %d", i, got, want) } } if set.TakeMin(&got) { t.Errorf("%s.TakeMin returned true", set, got) } if err := set.Check(); err != nil { t.Fatalf("check: %s: %#v", err, &set) } } func TestMinAndMax(t *testing.T) { values := []int{0, 456, 123, 789, -123} // elt 0 => empty set wantMax := []int{intsets.MinInt, 456, 456, 789, 789} wantMin := []int{intsets.MaxInt, 456, 123, 123, -123} var set intsets.Sparse for i, x := range values { if i != 0 { set.Insert(x) } if got, want := set.Min(), wantMin[i]; got != want { t.Errorf("Min #%d: got %d, want %d", i, got, want) } if got, want := set.Max(), wantMax[i]; got != want { t.Errorf("Max #%d: got %d, want %d", i, got, want) } } set.Insert(intsets.MinInt) if got, want := set.Min(), intsets.MinInt; got != want { t.Errorf("Min: got %d, want %d", got, want) } set.Insert(intsets.MaxInt) if got, want := set.Max(), intsets.MaxInt; got != want { t.Errorf("Max: got %d, want %d", got, want) } } func TestEquals(t *testing.T) { var setX intsets.Sparse setX.Insert(456) setX.Insert(123) setX.Insert(789) if !setX.Equals(&setX) { t.Errorf("Equals(%s, %s): got false", &setX, &setX) } var setY intsets.Sparse setY.Insert(789) setY.Insert(456) setY.Insert(123) if !setX.Equals(&setY) { t.Errorf("Equals(%s, %s): got false", &setX, &setY) } setY.Insert(1) if setX.Equals(&setY) { t.Errorf("Equals(%s, %s): got true", &setX, &setY) } var empty intsets.Sparse if setX.Equals(&empty) { t.Errorf("Equals(%s, %s): got true", &setX, &empty) } // Edge case: some block (with offset=0) appears in X but not Y. setY.Remove(123) if setX.Equals(&setY) { t.Errorf("Equals(%s, %s): got true", &setX, &setY) } } // A pset is a parallel implementation of a set using both an intsets.Sparse // and a built-in hash map. type pset struct { hash map[int]bool bits intsets.Sparse } func makePset() *pset { return &pset{hash: make(map[int]bool)} } func (set *pset) add(n int) { prev := len(set.hash) set.hash[n] = true grewA := len(set.hash) > prev grewB := set.bits.Insert(n) if grewA != grewB { panic(fmt.Sprintf("add(%d): grewA=%t grewB=%t", n, grewA, grewB)) } } func (set *pset) remove(n int) { prev := len(set.hash) delete(set.hash, n) shrankA := len(set.hash) < prev shrankB := set.bits.Remove(n) if shrankA != shrankB { panic(fmt.Sprintf("remove(%d): shrankA=%t shrankB=%t", n, shrankA, shrankB)) } } func (set *pset) check(t *testing.T, msg string) { var eltsA []int for elt := range set.hash { eltsA = append(eltsA, int(elt)) } sort.Ints(eltsA) eltsB := set.bits.AppendTo(nil) if a, b := fmt.Sprint(eltsA), fmt.Sprint(eltsB); a != b { t.Errorf("check(%s): hash=%s bits=%s (%s)", msg, a, b, &set.bits) } if err := set.bits.Check(); err != nil { t.Fatalf("Check(%s): %s: %#v", msg, err, &set.bits) } } // randomPset returns a parallel set of random size and elements. func randomPset(prng *rand.Rand, maxSize int) *pset { set := makePset() size := int(prng.Int()) % maxSize for i := 0; i < size; i++ { // TODO(adonovan): benchmark how performance varies // with this sparsity parameter. n := int(prng.Int()) % 10000 set.add(n) } return set } // TestRandomMutations performs the same random adds/removes on two // set implementations and ensures that they compute the same result. func TestRandomMutations(t *testing.T) { const debug = false set := makePset() prng := rand.New(rand.NewSource(0)) for i := 0; i < 10000; i++ { n := int(prng.Int())%2000 - 1000 if i%2 == 0 { if debug { log.Printf("add %d", n) } set.add(n) } else { if debug { log.Printf("remove %d", n) } set.remove(n) } if debug { set.check(t, "post mutation") } } set.check(t, "final") if debug { log.Print(&set.bits) } } // TestSetOperations exercises classic set operations: ∩ , ∪, \. func TestSetOperations(t *testing.T) { prng := rand.New(rand.NewSource(0)) // Use random sets of sizes from 0 to about 1000. // For each operator, we test variations such as // Z.op(X, Y), Z.op(X, Z) and Z.op(Z, Y) to exercise // the degenerate cases of each method implementation. for i := uint(0); i < 12; i++ { X := randomPset(prng, 1<, == cases in IntersectionWith that the // TestSetOperations data is too dense to cover. var X, Y intsets.Sparse X.Insert(1) X.Insert(1000) X.Insert(8000) Y.Insert(1) Y.Insert(2000) Y.Insert(4000) X.IntersectionWith(&Y) if got, want := X.String(), "{1}"; got != want { t.Errorf("IntersectionWith: got %s, want %s", got, want) } } func TestBitString(t *testing.T) { for _, test := range []struct { input []int want string }{ {nil, "0"}, {[]int{0}, "1"}, {[]int{0, 4, 5}, "110001"}, {[]int{0, 7, 177}, "1" + strings.Repeat("0", 169) + "10000001"}, {[]int{-3, 0, 4, 5}, "110001.001"}, {[]int{-3}, "0.001"}, } { var set intsets.Sparse for _, x := range test.input { set.Insert(x) } if got := set.BitString(); got != test.want { t.Errorf("BitString(%s) = %s, want %s", set.String(), got, test.want) } } } func TestFailFastOnShallowCopy(t *testing.T) { var x intsets.Sparse x.Insert(1) y := x // shallow copy (breaks representation invariants) defer func() { got := fmt.Sprint(recover()) want := "A Sparse has been copied without (*Sparse).Copy()" if got != want { t.Errorf("shallow copy: recover() = %q, want %q", got, want) } }() y.String() // panics t.Error("didn't panic as expected") } // -- Benchmarks ------------------------------------------------------- // TODO(adonovan): // - Gather set distributions from pointer analysis. // - Measure memory usage. func BenchmarkSparseBitVector(b *testing.B) { prng := rand.New(rand.NewSource(0)) for tries := 0; tries < b.N; tries++ { var x, y, z intsets.Sparse for i := 0; i < 1000; i++ { n := int(prng.Int()) % 100000 if i%2 == 0 { x.Insert(n) } else { y.Insert(n) } } z.Union(&x, &y) z.Difference(&x, &y) } } func BenchmarkHashTable(b *testing.B) { prng := rand.New(rand.NewSource(0)) for tries := 0; tries < b.N; tries++ { x, y, z := make(map[int]bool), make(map[int]bool), make(map[int]bool) for i := 0; i < 1000; i++ { n := int(prng.Int()) % 100000 if i%2 == 0 { x[n] = true } else { y[n] = true } } // union for n := range x { z[n] = true } for n := range y { z[n] = true } // difference z = make(map[int]bool) for n := range y { if !x[n] { z[n] = true } } } } func BenchmarkAppendTo(b *testing.B) { prng := rand.New(rand.NewSource(0)) var x intsets.Sparse for i := 0; i < 1000; i++ { x.Insert(int(prng.Int()) % 10000) } var space [1000]int for tries := 0; tries < b.N; tries++ { x.AppendTo(space[:0]) } }