Merge remote-tracking branch 'origin/topic/timw/dict-unit-tests'

* origin/topic/timw/dict-unit-tests:
  Reset the number of entries in a dict when calling Clear()
  Code cleanup in Dict.h
  Add unit testing for the public Dictionary API
This commit is contained in:
Jon Siwek 2020-02-13 19:05:56 -08:00
commit a5166086db
4 changed files with 181 additions and 51 deletions

View file

@ -1,4 +1,12 @@
3.2.0-dev.25 | 2020-02-13 19:05:56 -0800
* Reset the number of entries in a dict when calling Clear() (Tim Wojtulewicz, Corelight)
* Code cleanup in Dict.h (Tim Wojtulewicz, Corelight)
* Add unit testing for the public Dictionary API (Tim Wojtulewicz, Corelight)
3.2.0-dev.21 | 2020-02-13 17:14:26 -0800 3.2.0-dev.21 | 2020-02-13 17:14:26 -0800
* Check for failure when registering event manager with iosource manager * Check for failure when registering event manager with iosource manager

View file

@ -1 +1 @@
3.2.0-dev.21 3.2.0-dev.25

View file

@ -6,6 +6,8 @@
#include <memory.h> #include <memory.h>
#endif #endif
#include "3rdparty/doctest.h"
#include "Dict.h" #include "Dict.h"
#include "Reporter.h" #include "Reporter.h"
@ -21,10 +23,11 @@
// increase the size of the hash table as needed. // increase the size of the hash table as needed.
#define DEFAULT_DICT_SIZE 16 #define DEFAULT_DICT_SIZE 16
TEST_SUITE_BEGIN("Dict");
class DictEntry { class DictEntry {
public: public:
DictEntry(void* k, int l, hash_t h, void* val) DictEntry(void* k, int l, hash_t h, void* val) : key(k), len(l), hash(h), value(val) {}
{ key = k; len = l; hash = h; value = val; }
~DictEntry() ~DictEntry()
{ {
@ -41,38 +44,152 @@ public:
// bucket at which to start looking for the next value to return. // bucket at which to start looking for the next value to return.
class IterCookie { class IterCookie {
public: public:
IterCookie(int b, int o) IterCookie(int b, int o) : bucket(b), offset(o) {}
{
bucket = b;
offset = o;
ttbl = 0;
num_buckets_p = 0;
}
int bucket, offset; int bucket, offset;
PList<DictEntry>** ttbl; PList<DictEntry>** ttbl = nullptr;
const int* num_buckets_p; const int* num_buckets_p = nullptr;
PList<DictEntry> inserted; // inserted while iterating PList<DictEntry> inserted; // inserted while iterating
}; };
TEST_CASE("dict construction")
{
PDict<int> dict;
CHECK(dict.IsOrdered() == false);
CHECK(dict.Length() == 0);
PDict<int> dict2(ORDERED);
CHECK(dict2.IsOrdered() == true);
CHECK(dict2.Length() == 0);
}
TEST_CASE("dict operation")
{
PDict<uint32_t> dict;
uint32_t val = 10;
uint32_t key_val = 5;
HashKey* key = new HashKey(key_val);
dict.Insert(key, &val);
CHECK(dict.Length() == 1);
HashKey* key2 = new HashKey(key_val);
uint32_t* lookup = dict.Lookup(key2);
CHECK(*lookup == val);
dict.Remove(key2);
CHECK(dict.Length() == 0);
uint32_t* lookup2 = dict.Lookup(key2);
CHECK(lookup2 == (uint32_t*)0);
delete key2;
CHECK(dict.MaxLength() == 1);
CHECK(dict.NumCumulativeInserts() == 1);
dict.Insert(key, &val);
dict.Remove(key);
CHECK(dict.MaxLength() == 1);
CHECK(dict.NumCumulativeInserts() == 2);
uint32_t val2 = 15;
uint32_t key_val2 = 25;
key2 = new HashKey(key_val2);
dict.Insert(key, &val);
dict.Insert(key2, &val2);
CHECK(dict.Length() == 2);
CHECK(dict.NumCumulativeInserts() == 4);
dict.Clear();
CHECK(dict.Length() == 0);
delete key;
delete key2;
}
TEST_CASE("dict nthentry")
{
PDict<uint32_t> unordered(UNORDERED);
PDict<uint32_t> ordered(ORDERED);
uint32_t val = 15;
uint32_t key_val = 5;
HashKey* okey = new HashKey(key_val);
HashKey* ukey = new HashKey(key_val);
uint32_t val2 = 10;
uint32_t key_val2 = 25;
HashKey* okey2 = new HashKey(key_val2);
HashKey* ukey2 = new HashKey(key_val2);
unordered.Insert(ukey, &val);
unordered.Insert(ukey2, &val2);
ordered.Insert(okey, &val);
ordered.Insert(okey2, &val2);
// NthEntry returns null for unordered dicts
uint32_t* lookup = unordered.NthEntry(0);
CHECK(lookup == (uint32_t*)0);
// Ordered dicts are based on order of insertion, nothing about the
// data itself
lookup = ordered.NthEntry(0);
CHECK(*lookup == 15);
delete okey;
delete okey2;
delete ukey;
delete ukey2;
}
TEST_CASE("dict iteration")
{
PDict<uint32_t> dict;
uint32_t val = 15;
uint32_t key_val = 5;
HashKey* key = new HashKey(key_val);
uint32_t val2 = 10;
uint32_t key_val2 = 25;
HashKey* key2 = new HashKey(key_val2);
dict.Insert(key, &val);
dict.Insert(key2, &val2);
HashKey* it_key;
IterCookie* it = dict.InitForIteration();
CHECK(it != nullptr);
int count = 0;
while ( uint32_t* entry = dict.NextEntry(it_key, it) )
{
if ( count == 0 )
{
CHECK(it_key->Hash() == key2->Hash());
CHECK(*entry == 10);
}
else
{
CHECK(it_key->Hash() == key->Hash());
CHECK(*entry == 15);
}
count++;
delete it_key;
}
delete key;
delete key2;
}
Dictionary::Dictionary(dict_order ordering, int initial_size) Dictionary::Dictionary(dict_order ordering, int initial_size)
{ {
tbl = 0;
tbl2 = 0;
if ( ordering == ORDERED ) if ( ordering == ORDERED )
order = new PList<DictEntry>; order = new PList<DictEntry>;
else
order = 0;
delete_func = 0;
tbl_next_ind = 0;
cumulative_entries = 0;
num_buckets = num_entries = max_num_entries = thresh_entries = 0;
den_thresh = 0;
num_buckets2 = num_entries2 = max_num_entries2 = thresh_entries2 = 0;
den_thresh2 = 0;
if ( initial_size > 0 ) if ( initial_size > 0 )
Init(initial_size); Init(initial_size);
@ -87,8 +204,10 @@ Dictionary::~Dictionary()
void Dictionary::Clear() void Dictionary::Clear()
{ {
DeInit(); DeInit();
tbl = 0; tbl = nullptr;
tbl2 = 0; tbl2 = nullptr;
num_entries = 0;
num_entries2 = 0;
} }
void Dictionary::DeInit() void Dictionary::DeInit()
@ -111,8 +230,9 @@ void Dictionary::DeInit()
} }
delete [] tbl; delete [] tbl;
tbl = nullptr;
if ( tbl2 == 0 ) if ( ! tbl2 )
return; return;
for ( int i = 0; i < num_buckets2; ++i ) for ( int i = 0; i < num_buckets2; ++i )
@ -130,7 +250,7 @@ void Dictionary::DeInit()
} }
delete [] tbl2; delete [] tbl2;
tbl2 = 0; tbl2 = nullptr;
} }
void* Dictionary::Lookup(const void* key, int key_size, hash_t hash) const void* Dictionary::Lookup(const void* key, int key_size, hash_t hash) const
@ -386,7 +506,7 @@ void Dictionary::Init(int size)
tbl = new PList<DictEntry>*[num_buckets]; tbl = new PList<DictEntry>*[num_buckets];
for ( int i = 0; i < num_buckets; ++i ) for ( int i = 0; i < num_buckets; ++i )
tbl[i] = 0; tbl[i] = nullptr;
max_num_entries = num_entries = 0; max_num_entries = num_entries = 0;
SetDensityThresh(DEFAULT_DENSITY_THRESH); SetDensityThresh(DEFAULT_DENSITY_THRESH);
@ -398,7 +518,7 @@ void Dictionary::Init2(int size)
tbl2 = new PList<DictEntry>*[num_buckets2]; tbl2 = new PList<DictEntry>*[num_buckets2];
for ( int i = 0; i < num_buckets2; ++i ) for ( int i = 0; i < num_buckets2; ++i )
tbl2[i] = 0; tbl2[i] = nullptr;
max_num_entries2 = num_entries2 = 0; max_num_entries2 = num_entries2 = 0;
} }
@ -575,7 +695,7 @@ void Dictionary::FinishChangeSize()
delete [] tbl; delete [] tbl;
tbl = tbl2; tbl = tbl2;
tbl2 = 0; tbl2 = nullptr;
num_buckets = num_buckets2; num_buckets = num_buckets2;
num_entries = num_entries2; num_entries = num_entries2;
@ -632,3 +752,5 @@ void generic_delete_func(void* v)
{ {
free(v); free(v);
} }
TEST_SUITE_END();

View file

@ -11,7 +11,7 @@ class IterCookie;
// Type indicating whether the dictionary should keep track of the order // Type indicating whether the dictionary should keep track of the order
// of insertions. // of insertions.
typedef enum { ORDERED, UNORDERED } dict_order; enum dict_order { ORDERED, UNORDERED };
// Type for function to be called when deleting elements. // Type for function to be called when deleting elements.
typedef void (*dict_delete_func)(void*); typedef void (*dict_delete_func)(void*);
@ -70,7 +70,7 @@ public:
} }
// True if the dictionary is ordered, false otherwise. // True if the dictionary is ordered, false otherwise.
int IsOrdered() const { return order != 0; } bool IsOrdered() const { return order != 0; }
// If the dictionary is ordered then returns the n'th entry's value; // If the dictionary is ordered then returns the n'th entry's value;
// the second method also returns the key. The first entry inserted // the second method also returns the key. The first entry inserted
@ -158,26 +158,26 @@ private:
// When we're resizing, we'll have tbl (old) and tbl2 (new) // When we're resizing, we'll have tbl (old) and tbl2 (new)
// tbl_next_ind keeps track of how much we've moved to tbl2 // tbl_next_ind keeps track of how much we've moved to tbl2
// (it's the next index we're going to move). // (it's the next index we're going to move).
PList<DictEntry>** tbl; PList<DictEntry>** tbl = nullptr;
int num_buckets; int num_buckets = 0;
int num_entries; int num_entries = 0;
int max_num_entries; int max_num_entries = 0;
uint64_t cumulative_entries; uint64_t cumulative_entries = 0;
double den_thresh; double den_thresh = 0.0;
int thresh_entries; int thresh_entries = 0;
// Resizing table (replicates tbl above). // Resizing table (replicates tbl above).
PList<DictEntry>** tbl2; PList<DictEntry>** tbl2 = nullptr;
int num_buckets2; int num_buckets2 = 0;
int num_entries2; int num_entries2 = 0;
int max_num_entries2; int max_num_entries2 = 0;
double den_thresh2; double den_thresh2 = 0;
int thresh_entries2; int thresh_entries2 = 0;
hash_t tbl_next_ind; hash_t tbl_next_ind = 0;
PList<DictEntry>* order; PList<DictEntry>* order = nullptr;
dict_delete_func delete_func; dict_delete_func delete_func = nullptr;
PList<IterCookie> cookies; PList<IterCookie> cookies;
}; };