func TestInsert(t *testing.T) { h := offheap.NewHashTable(8) cv.Convey("inserting a non-zero key should enable retrieving them with Lookup", t, func() { cv.So(h.Population, cv.ShouldEqual, 0) cv.So(h.Lookup(23), cv.ShouldEqual, nil) c, ok := h.Insert(23) c.SetInt(55) cv.So(c, cv.ShouldNotEqual, nil) cv.So(ok, cv.ShouldEqual, true) cv.So(h.Population, cv.ShouldEqual, 1) cv.So(h.Lookup(23), cv.ShouldNotEqual, nil) cell := h.Lookup(23) cv.So(cell.Value[0], cv.ShouldEqual, 55) }) h.Clear() cv.Convey("inserting a zero key should also be retrievable with Lookup", t, func() { cv.So(h.Population, cv.ShouldEqual, 0) cv.So(h.Lookup(0), cv.ShouldEqual, nil) c, ok := h.Insert(0) c.SetInt(55) cv.So(c, cv.ShouldNotEqual, nil) cv.So(ok, cv.ShouldEqual, true) cv.So(h.Population, cv.ShouldEqual, 1) cv.So(h.Lookup(0), cv.ShouldNotEqual, nil) cell := h.Lookup(0) cv.So(cell.Value[0], cv.ShouldEqual, 55) }) h.Clear() cv.Convey("Insert()-ing the same key twice should return false for the 2nd param on encountering the same key again. For 0 key.", t, func() { cv.So(h.Population, cv.ShouldEqual, 0) c, ok := h.Insert(0) cv.So(c, cv.ShouldNotEqual, nil) cv.So(c.UnHashedKey, cv.ShouldEqual, 0) cv.So(ok, cv.ShouldEqual, true) c, ok = h.Insert(0) cv.So(c, cv.ShouldNotEqual, nil) cv.So(c.UnHashedKey, cv.ShouldEqual, 0) cv.So(ok, cv.ShouldEqual, false) }) h.Clear() cv.Convey("Insert()-ing the same key twice should return false for the 2nd param on encountering the same key again. For not-zero key.", t, func() { cv.So(h.Population, cv.ShouldEqual, 0) c, ok := h.Insert(1) cv.So(c, cv.ShouldNotEqual, nil) cv.So(c.UnHashedKey, cv.ShouldEqual, 1) cv.So(ok, cv.ShouldEqual, true) c, ok = h.Insert(1) cv.So(c, cv.ShouldNotEqual, nil) cv.So(c.UnHashedKey, cv.ShouldEqual, 1) cv.So(ok, cv.ShouldEqual, false) }) }
func TestCompact(t *testing.T) { cv.Convey("Given a big table with no values it, Compact() should re-allocate it to be smaller", t, func() { h := offheap.NewHashTable(128) cv.So(h.ArraySize, cv.ShouldEqual, 128) cv.So(h.Population, cv.ShouldEqual, 0) h.Compact() cv.So(h.ArraySize, cv.ShouldEqual, 1) cv.So(h.Population, cv.ShouldEqual, 0) }) }
func TestDelete(t *testing.T) { h := offheap.NewHashTable(2) h.Clear() cv.Convey("delete should eliminate an element, leaving the rest", t, func() { N := 5 // fill up a table cv.So(h.Population, cv.ShouldEqual, 0) for i := 0; i < N; i++ { a, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) cell := h.Lookup(uint64(i)) cv.So(cell, cv.ShouldNotEqual, nil) cv.So(cell, cv.ShouldEqual, a) for j := i + 1; j < N+10; j++ { cell = h.Lookup(uint64(j)) cv.So(cell, cv.ShouldEqual, nil) } } // now delete from it, checking all the way down for correctness for i := -10; i < N; i++ { h.DeleteKey(uint64(i)) if i >= 0 { cell := h.Lookup(uint64(i)) cv.So(cell, cv.ShouldEqual, nil) for j := i + 1; j < N; j++ { cell = h.Lookup(uint64(j)) cv.So(cell, cv.ShouldNotEqual, nil) cv.So(cell.UnHashedKey, cv.ShouldEqual, j) } } else { cv.So(h.Population, cv.ShouldEqual, N) } } cell := h.Lookup(uint64(N + 1)) cv.So(cell, cv.ShouldEqual, nil) }) }
func TestCompatAfterDelete(t *testing.T) { h := offheap.NewHashTable(2) h.Clear() cv.Convey("after being filled up and then deleted down to just 2 elements, Compact() should reduce table size to 4", t, func() { N := 100 // fill up a table cv.So(h.Population, cv.ShouldEqual, 0) for i := 0; i < N; i++ { h.Insert(uint64(i)) } // now delete from it for i := 0; i < N-2; i++ { h.DeleteKey(uint64(i)) } cv.So(h.ArraySize, cv.ShouldEqual, 256) h.Compact() cv.So(h.ArraySize, cv.ShouldEqual, 4) }) }
func TestGrowth(t *testing.T) { h := offheap.NewHashTable(2) cv.Convey("Given a size 2 table, inserting 0 and 1 should retain and recall the 0 and the 1 keys", t, func() { cv.So(h.Population, cv.ShouldEqual, 0) for i := 0; i < 2; i++ { _, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) } cv.So(h.Population, cv.ShouldEqual, 2) cv.So(h.ArraySize, cv.ShouldEqual, 4) }) h.Clear() cv.Convey("inserting more than the current size should automatically grow the table", t, func() { N := 100 cv.So(h.Population, cv.ShouldEqual, 0) for i := 0; i < N; i++ { _, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) } cv.So(h.Population, cv.ShouldEqual, N) cv.So(h.ArraySize, cv.ShouldEqual, upper_power_of_two(uint64(float64(N)*4.0/3.0))) for i := 0; i < N; i++ { cell := h.Lookup(uint64(i)) cv.So(cell, cv.ShouldNotEqual, nil) cv.So(cell.UnHashedKey, cv.ShouldEqual, i) } cell := h.Lookup(uint64(N + 1)) cv.So(cell, cv.ShouldEqual, nil) }) }
func TestIterator(t *testing.T) { cv.Convey("Given a table with 0,1,2 in it, the Iterator should give all three values back", t, func() { h := offheap.NewHashTable(8) cv.So(h.Population, cv.ShouldEqual, 0) for i := 0; i < 3; i++ { _, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) if i == 0 { // iterator should start with the zero value it := h.NewIterator() cv.So(it.Cur.UnHashedKey, cv.ShouldEqual, 0) } } cv.So(h.Population, cv.ShouldEqual, 3) found := []uint64{} for it := h.NewIterator(); it.Cur != nil; it.Next() { found = append(found, it.Cur.UnHashedKey) } cv.So(len(found), cv.ShouldEqual, 3) cv.So(found, cv.ShouldContain, 0) cv.So(found, cv.ShouldContain, 1) cv.So(found, cv.ShouldContain, 2) }) cv.Convey("Given a table with 1,2,3 in it, the Iterator should give all three values back", t, func() { h := offheap.NewHashTable(8) cv.So(h.Population, cv.ShouldEqual, 0) for i := 1; i < 4; i++ { _, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) if i == 0 { // iterator should not start with the zero value, not inserted. it := h.NewIterator() cv.So(it.Cur.UnHashedKey, cv.ShouldEqual, 1) } } cv.So(h.Population, cv.ShouldEqual, 3) found := []uint64{} for it := h.NewIterator(); it.Cur != nil; it.Next() { found = append(found, it.Cur.UnHashedKey) } cv.So(len(found), cv.ShouldEqual, 3) cv.So(found, cv.ShouldContain, 3) cv.So(found, cv.ShouldContain, 1) cv.So(found, cv.ShouldContain, 2) }) cv.Convey("Given a table with the regular 0-th slot and the special zero-location spot occupied, then the the Iterator should still give all the values back", t, func() { h := offheap.NewHashTable(4) cv.So(h.Population, cv.ShouldEqual, 0) for i := 0; i < 2; i++ { _, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) if i == 0 { // iterator should start with the zero value it := h.NewIterator() cv.So(it.Cur.UnHashedKey, cv.ShouldEqual, 0) } } cv.So(h.Population, cv.ShouldEqual, 2) cv.So(h.CellAt(0).UnHashedKey, cv.ShouldEqual, 1) // important for this test that the regular 0-th (first) cell slot be occupied. found := []uint64{} for it := h.NewIterator(); it.Cur != nil; it.Next() { found = append(found, it.Cur.UnHashedKey) } cv.So(len(found), cv.ShouldEqual, 2) cv.So(found, cv.ShouldContain, 0) cv.So(found, cv.ShouldContain, 1) }) cv.Convey("Given a table with the regular 0-th slot *empty* and the special zero-location spot occupied, then the the Iterator should still give all the values back", t, func() { h := offheap.NewHashTable(8) cv.So(h.Population, cv.ShouldEqual, 0) for i := 0; i < 2; i++ { _, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) if i == 0 { // iterator should start with the zero value it := h.NewIterator() cv.So(it.Cur.UnHashedKey, cv.ShouldEqual, 0) cv.So(it.Pos, cv.ShouldEqual, -1) } } cv.So(h.Population, cv.ShouldEqual, 2) cv.So(h.CellAt(0).UnHashedKey, cv.ShouldEqual, 0) // important for this test that the regular 0-th (first) cell slot be *empty*. found := []uint64{} for it := h.NewIterator(); it.Cur != nil; it.Next() { found = append(found, it.Cur.UnHashedKey) } cv.So(len(found), cv.ShouldEqual, 2) cv.So(found, cv.ShouldContain, 0) cv.So(found, cv.ShouldContain, 1) }) cv.Convey("Given a table with the regular 0-th slot *filled* and the special zero-location spot *empty*, then the the Iterator should still give the one value back", t, func() { // size 4 just happens to generate an occupation at h.Cells[0] h := offheap.NewHashTable(4) cv.So(h.Population, cv.ShouldEqual, 0) i := 1 _, ok := h.Insert(uint64(i)) cv.So(ok, cv.ShouldEqual, true) // iterator should start with the zero value it := h.NewIterator() cv.So(it.Cur.UnHashedKey, cv.ShouldEqual, 1) cv.So(it.Pos, cv.ShouldEqual, 0) cv.So(h.Population, cv.ShouldEqual, 1) cv.So(h.CellAt(0).UnHashedKey, cv.ShouldEqual, 1) // important for this test that the regular 0-th (first) cell slot be *filled*. found := []uint64{} for it := h.NewIterator(); it.Cur != nil; it.Next() { found = append(found, it.Cur.UnHashedKey) } cv.So(len(found), cv.ShouldEqual, 1) cv.So(found, cv.ShouldContain, 1) }) cv.Convey("Given an empty table, an Iterator should still work fine, without crashing", t, func() { h := offheap.NewHashTable(4) cv.So(h.Population, cv.ShouldEqual, 0) found := []uint64{} for it := h.NewIterator(); it.Cur != nil; it.Next() { found = append(found, it.Cur.UnHashedKey) } cv.So(len(found), cv.ShouldEqual, 0) }) }
func TestRandomOperationsOrder(t *testing.T) { h := offheap.NewHashTable(2) m := make(map[uint64]int) cv.Convey("given a sequence of random operations, the result should match what Go's builtin map does", t, func() { cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) // basic insert m[1] = 2 h.InsertIntValue(1, 2) cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) m[3] = 4 h.InsertIntValue(3, 4) cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) // basic delete delete(m, 1) h.DeleteKey(1) cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) delete(m, 3) h.DeleteKey(3) cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) // now do random operations N := 1000 seed := time.Now().UnixNano() fmt.Printf("\n TestRandomOperationsOrder() using seed = '%v'\n", seed) gen := rand.New(rand.NewSource(seed)) for i := 0; i < N; i++ { op := gen.Int() % 4 k := uint64(gen.Int() % (N / 4)) v := gen.Int() % (N / 4) switch op { case 0, 1, 2: h.InsertIntValue(k, v) m[k] = v cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) case 3: h.DeleteKey(uint64(k)) delete(m, k) cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) } } // distribution more emphasizing deletes for i := 0; i < N; i++ { op := gen.Int() % 2 k := uint64(gen.Int() % (N / 5)) v := gen.Int() % (N / 2) switch op { case 0: h.InsertIntValue(k, v) m[k] = v cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) case 1: h.DeleteKey(uint64(k)) delete(m, k) cv.So(hashEqualsMap(h, m), cv.ShouldEqual, true) } } }) }