From 540368cd2a0a3e00224ddec8cdf16d2eab250574 Mon Sep 17 00:00:00 2001 From: Ishan Bhanuka Date: Wed, 23 Sep 2020 23:41:32 +0530 Subject: [PATCH] Add hashtable tests for Create, Insert, Delete Add comments and test case fail messages --- config/hashtable_config.go | 2 +- pkg/hashtable/hashtable.go | 27 +++++++-- pkg/hashtable/hashtable_test.go | 97 ++++++++++++++++++++++++++------- 3 files changed, 98 insertions(+), 28 deletions(-) diff --git a/config/hashtable_config.go b/config/hashtable_config.go index dcea37d..068960b 100644 --- a/config/hashtable_config.go +++ b/config/hashtable_config.go @@ -1,7 +1,7 @@ package config var ( - HASHTABLE_GROW_FACTOR uint64 = 2 + HASHTABLE_GROW_FACTOR float64 = 2.0 HASHTABLE_SHRINK_FACTOR float64 = 0.5 HASHTABLE_GROW_LIMIT uint64 = 1048576 HASHTABLE_SHRINK_LIMIT uint64 = 32 diff --git a/pkg/hashtable/hashtable.go b/pkg/hashtable/hashtable.go index 7bb517b..094b3e7 100644 --- a/pkg/hashtable/hashtable.go +++ b/pkg/hashtable/hashtable.go @@ -13,7 +13,8 @@ type HashTable struct { deletes uint64 } -func CreateHashTable(size uint64) *HashTable { +// Create hashtable with number of buckets given by size +func Create(size uint64) *HashTable { var newHashTable = HashTable{table: make([]*ll.LinkedList, size), buckets: size, inserts: 0, deletes: 0} for i := 0; i < int(size); i++ { newHashTable.table[i] = ll.CreateLinkedList() @@ -22,6 +23,7 @@ func CreateHashTable(size uint64) *HashTable { return &newHashTable } +// Insert value into hashtable func (ht *HashTable) Insert(key uint64, value uint64) { keyBytes := helper.IntToByte(key) hashKey := helper.HashKey(keyBytes, ht.buckets) @@ -29,19 +31,25 @@ func (ht *HashTable) Insert(key uint64, value uint64) { bucket := ht.table[hashKey] bucket.Append(keyBytes, valueBytes) - ht.inserts += 1 + ht.inserts++ } +// Delete all values for the given key from +// hashtable. Return number of values deleted +// and true if no values were deleted func (ht *HashTable) Delete(key uint64) (int, bool) { keyBytes := helper.IntToByte(key) hashKey := helper.HashKey(keyBytes, ht.buckets) bucket := ht.table[hashKey] count, status := bucket.Delete(keyBytes) - ht.deletes -= uint64(count) + ht.deletes += uint64(count) return count, status } +// Get all the values for the given key stored +// in the table. Return array of values and status +// true if there are no value to return func (ht *HashTable) Get(key uint64) ([]uint64, bool) { keyBytes := helper.IntToByte(key) hashKey := helper.HashKey(keyBytes, ht.buckets) @@ -57,8 +65,10 @@ func (ht *HashTable) Get(key uint64) ([]uint64, bool) { return objects, status } +// Grow grows the current hashtable by a constant factor +// it returns reference to new hashtable func (ht *HashTable) Grow() *HashTable { - var newBuckets = uint64(ht.buckets * 2) + var newBuckets uint64 = uint64(float64(ht.buckets) * config.HASHTABLE_GROW_FACTOR) // Do note shrink below limit if newBuckets <= config.HASHTABLE_GROW_LIMIT { @@ -68,8 +78,10 @@ func (ht *HashTable) Grow() *HashTable { } } +// Shrink shrinks the current hashtable by a constant factor +// it returns reference to new hashtable func (ht *HashTable) Shrink() *HashTable { - var newBuckets = uint64(ht.buckets / 2) + var newBuckets uint64 = uint64(float64(ht.buckets) * config.HASHTABLE_SHRINK_FACTOR) // Do note shrink below limit if newBuckets <= config.HASHTABLE_SHRINK_LIMIT { @@ -79,8 +91,11 @@ func (ht *HashTable) Shrink() *HashTable { } } +// Resize given hashtable to new hashtable of given size +// copies all values from existing hash table to new hashtable +// returns reference to new hashtable func (ht *HashTable) Resize(size uint64) *HashTable { - var newHt = CreateHashTable(size) + var newHt = Create(size) // iterate over all values and insert into new hash table diff --git a/pkg/hashtable/hashtable_test.go b/pkg/hashtable/hashtable_test.go index e4c40cb..c36ccc8 100644 --- a/pkg/hashtable/hashtable_test.go +++ b/pkg/hashtable/hashtable_test.go @@ -1,16 +1,15 @@ package hashtable import ( + "bytes" "testing" "github.com/neelkshah/pandora/pkg/helper" - - "github.com/neelkshah/pandora/config" ) -func TestCreateHashTable(t *testing.T) { - var response = *CreateHashTable() - if response.buckets != uint32(config.HASHTABLE_INIT_SIZE) || response.inserts != 0 || response.deletes != 0 { +func TestCreate(t *testing.T) { + var response = *Create(10) + if response.buckets != 10 || response.inserts != 0 || response.deletes != 0 { t.Fatalf("Fail at values") } @@ -22,32 +21,88 @@ func TestCreateHashTable(t *testing.T) { } func TestInsert(t *testing.T) { + // Create and insert value in hashtable var key, value uint64 = 5, 10 - var table = *CreateHashTable() + var hashTable = *Create(10) + hashTable.Insert(5, 10) + + // checked linkedlist for successful insertion var keyBytes = helper.IntToByte(uint64(key)) - var hashKey = helper.HashKey(keyBytes, table.buckets) + var hashKey = helper.HashKey(keyBytes, hashTable.buckets) valueBytes := helper.IntToByte(value) - bucket := table.table[hashKey] + bucket := hashTable.table[hashKey] + response, status := bucket.Get(keyBytes) + + // checks + // TODO: A better way to map tests to error messages? + if status { + t.Fatalf("LinkedList does not contain anything insertion failed") + } - table.Insert(5, 10) + if len(response) != 1 { + t.Fatalf("Linkedlist should contain only one value after single insert") + } + + if bytes.Compare(response[0], valueBytes) != 0 { + t.Fatalf("Hashtable did not store correct value") + } - // TODO: how to check if number is inserted - // check linkedlist using its APIs - // or retrieive it from linkedlist and check + if hashTable.inserts != 1 { + t.Fatalf("Insert count failed") + } } func TestGet(t *testing.T) { + // create hashtable and insert value into underlying linkedlist var key, value, size uint64 = 5, 10, 10 - var table = *CreateHashTable(size) - var keyBytes = helper.IntToByte(uint64(key)) - var hashKey = helper.HashKey(keyBytes, table.buckets) + var hashTable = *Create(size) + var keyBytes = helper.IntToByte(key) + var hashKey = helper.HashKey(keyBytes, hashTable.buckets) valueBytes := helper.IntToByte(value) - bucket := table.table[hashKey] + bucket := hashTable.table[hashKey] + bucket.Append(keyBytes, valueBytes) + + // get value from linkedlist + var response, status = hashTable.Get(key) - table.Insert(5, 10) + // checks + // TODO: A better way to map tests to error messages? + if status { + t.Fatalf("Get failed") + } + + if len(response) != 1 { + t.Fatalf("Get should return a single value. It returned %d values.", len(response)) + } - // TODO: how to test get - // insert using linkedlist API - // or directly insert into hashtable using insert - // but then that's not unit testing + if response[0] != value { + t.Fatalf("Get did not return the correct value. Value should be %d is %d", value, response[0]) + } +} + +func TestDelete(t *testing.T) { + // create hashtable and insert value into underlying linkedlist + var key, value, size uint64 = 5, 10, 10 + var hashTable = *Create(size) + var keyBytes = helper.IntToByte(key) + var hashKey = helper.HashKey(keyBytes, hashTable.buckets) + valueBytes := helper.IntToByte(value) + bucket := hashTable.table[hashKey] + bucket.Append(keyBytes, valueBytes) + + // delete value from hash table and check underlying linkedlist + var response, status = hashTable.Delete(key) + + // checks + if status { + t.Fatalf("Delete failed") + } + + if response != 1 { + t.Fatalf("Deleted %d values should have deleted 1 value", response) + } + + if hashTable.deletes != 1 { + t.Fatalf("Delete counter failed. It counted %d but should have counted 1", hashTable.deletes) + } }