Some checks failed
gcc-build / gcc-build (push) Has been cancelled
clang-build / clang-build (push) Has been cancelled
1. Fixed infinite loop issue that occurred when map was full of tombstones; 2. Fixed bug related to tombstone count decrement when adding a new key on a deleted slot; 3. Added proper map resize when keys are added or removed; 4. Fixed inconsistent error messages; 5. Added proper NULL check on map_remove method.
394 lines
9.2 KiB
C
394 lines
9.2 KiB
C
/*
|
|
* Unit tests for Map data type
|
|
*/
|
|
|
|
#define TEST(NAME) do { \
|
|
printf("Running test_%s...", #NAME); \
|
|
test_##NAME(); \
|
|
printf(" PASSED\n"); \
|
|
} while(0)
|
|
|
|
#include <stdio.h>
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
|
|
#include "../src/map.h"
|
|
|
|
// Create a new map
|
|
void test_map_new(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
assert(res.value.map != NULL);
|
|
assert(map_size(res.value.map) == 0);
|
|
assert(map_capacity(res.value.map) > 0);
|
|
|
|
map_destroy(res.value.map);
|
|
}
|
|
|
|
// Add elements to map
|
|
void test_map_add(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int x = 42, y = 84;
|
|
|
|
map_result_t x_res = map_add(map, "key1", (void*)&x);
|
|
assert(x_res.status == MAP_OK);
|
|
assert(map_size(map) == 1);
|
|
|
|
map_result_t y_res = map_add(map, "key2", (void*)&y);
|
|
assert(y_res.status == MAP_OK);
|
|
assert(map_size(map) == 2);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Add multiple elements to the map
|
|
void test_map_add_multiple(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int x = 0xB00B5;
|
|
const char *y = "Hello";
|
|
|
|
map_result_t add_res = map_add(map, "x", (void*)&x);
|
|
assert(add_res.status == MAP_OK);
|
|
|
|
add_res = map_add(map, "y", (void*)y);
|
|
assert(add_res.status == MAP_OK);
|
|
|
|
assert(map_size(map) == 2);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Get map element
|
|
void test_map_get(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int val = 123;
|
|
map_add(map, "test", (void*)&val);
|
|
|
|
map_result_t get_res = map_get(map, "test");
|
|
assert(get_res.status == MAP_OK);
|
|
assert(*(int*)get_res.value.element == 123);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Get non-existing key from map
|
|
void test_map_get_invalid(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
map_result_t get_res = map_get(map, "boom");
|
|
assert(get_res.status == MAP_ERR_NOT_FOUND);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Get from map full of deleted slots
|
|
// If the table contains no ENTRY_EMPTY slots
|
|
// (i.e., the table is full of ENTRY_DELETED slots),
|
|
// map_get and map_remove should NOT loop forever
|
|
void test_map_get_deleted_slots(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
// Fill INITIAL_CAP (=4) without trigger resizing
|
|
map_add(map, "x", (void*)1);
|
|
map_add(map, "y", (void*)2);
|
|
map_add(map, "z", (void*)3);
|
|
map_add(map, "j", (void*)4);
|
|
|
|
// Remove all ENTRY_OCCUPIED slots.
|
|
// This function should resize the map when the load factor is too big
|
|
// and should also garbage-collect all the ENTRY_DELETED entries.
|
|
// Tombstone count should therefore be equal to 3 and capacity should be doubled
|
|
map_remove(map, "x");
|
|
map_remove(map, "y");
|
|
map_remove(map, "z");
|
|
map_remove(map, "j");
|
|
|
|
assert(map->tombstone_count == 3);
|
|
assert(map->capacity == 8);
|
|
assert(map->size == 0);
|
|
|
|
// Retrieving a deleted element should return an error
|
|
// but should not loop forever
|
|
map_result_t get_deleted_res = map_get(map, "y");
|
|
assert(get_deleted_res.status == MAP_ERR_NOT_FOUND);
|
|
|
|
// Adding a new element should increase the size
|
|
// and should not loop forever
|
|
const int k = 5;
|
|
map_result_t add_res = map_add(map, "k", (void*)&k);
|
|
assert(add_res.status == MAP_OK);
|
|
|
|
assert(map->tombstone_count < map->capacity);
|
|
assert(map->capacity == 8);
|
|
assert(map->size == 1);
|
|
|
|
// Retrieving an ENTRY_OCCUPIED element should works normally
|
|
map_result_t get_res = map_get(map, "k");
|
|
assert(get_res.status == MAP_OK);
|
|
assert(*(int*)get_res.value.element == 5);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Map with heterogeneous types
|
|
void test_map_mixed(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int x = 0xB00B5;
|
|
const char *y = "Hello";
|
|
|
|
map_add(map, "x", (void*)&x);
|
|
map_add(map, "y", (void*)y);
|
|
|
|
map_result_t get_res = map_get(map, "x");
|
|
assert(get_res.status == MAP_OK);
|
|
|
|
const int *val_x = (const int*)get_res.value.element;
|
|
assert(*val_x == 0xB00B5);
|
|
|
|
get_res = map_get(map, "y");
|
|
assert(get_res.status == MAP_OK);
|
|
|
|
const char *val_y = (const char*)get_res.value.element;
|
|
assert(!strcmp(val_y, "Hello"));
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Update existing map key
|
|
void test_map_update(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int x = 100, y = 200;
|
|
|
|
map_add(map, "key", (void*)&x);
|
|
|
|
map_result_t set_res = map_add(map, "key", (void*)&y);
|
|
assert(set_res.status == MAP_OK);
|
|
|
|
map_result_t get_res = map_get(map, "key");
|
|
const int *val = (const int*)get_res.value.element;
|
|
|
|
assert(*val == 200);
|
|
assert(map_size(map) == 1);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Remove an element from map
|
|
void test_map_remove(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int x = 10, y = 20;
|
|
|
|
map_add(map, "x", (void*)&x);
|
|
map_add(map, "y", (void*)&y);
|
|
|
|
assert(map_size(map) == 2);
|
|
|
|
map_result_t rm_res = map_remove(map, "x");
|
|
assert(rm_res.status == MAP_OK);
|
|
assert(map_size(map) == 1);
|
|
|
|
// Check whether the 'x' and 'y' keys are still there
|
|
map_result_t get_res = map_get(map, "x");
|
|
assert(get_res.status != MAP_OK);
|
|
|
|
get_res = map_get(map, "y");
|
|
assert(get_res.status == MAP_OK);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Remove non-existing key from map
|
|
void test_map_remove_invalid(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
map_result_t rm_res = map_remove(map, "boom");
|
|
assert(rm_res.status != MAP_OK);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Clear the map
|
|
void test_map_clear(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int x = 10, y = 20, z = 30;
|
|
|
|
map_add(map, "x", (void*)&x);
|
|
map_add(map, "y", (void*)&y);
|
|
map_add(map, "z", (void*)&z);
|
|
|
|
assert(map_size(map) == 3);
|
|
|
|
map_result_t clear_res = map_clear(map);
|
|
assert(clear_res.status == MAP_OK);
|
|
assert(map_size(map) == 0);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Clear empty map
|
|
void test_map_clear_empty(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
map_result_t clear_res = map_clear(map);
|
|
assert(clear_res.status == MAP_OK);
|
|
assert(map_size(map) == 0);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Multiple operations in sequence (add, update, delete and clear)
|
|
void test_map_sequence(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const int x = 0xB00B5;
|
|
const char *y = "Hello";
|
|
|
|
// Add
|
|
map_add(map, "x", (void*)&x);
|
|
map_add(map, "y", (void*)&y);
|
|
|
|
assert(map_size(map) == 2);
|
|
|
|
// Update
|
|
const int new_x = 0xC0FFEE;
|
|
map_add(map, "x", (void*)&new_x);
|
|
|
|
map_result_t get_res = map_get(map, "x");
|
|
assert(*(const int*)get_res.value.element == 0xC0FFEE);
|
|
|
|
// Delete
|
|
map_remove(map, "y");
|
|
assert(map_size(map) == 1);
|
|
|
|
// Clear
|
|
map_clear(map);
|
|
assert(map_size(map) == 0);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Test map with product data types
|
|
typedef struct {
|
|
char name[256];
|
|
char surname[256];
|
|
short age;
|
|
} Person;
|
|
|
|
void test_map_struct(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
const Person bob = { "Bob", "Miller", 23 };
|
|
const Person alice = { "Alice", "Davis", 21 };
|
|
|
|
map_add(map, "af94rt", (void*)&bob);
|
|
map_add(map, "b910o5", (void*)&alice);
|
|
|
|
map_result_t get_res = map_get(map, "af94rt");
|
|
assert(get_res.status == MAP_OK);
|
|
|
|
const Person *retr = (const Person*)get_res.value.element;
|
|
assert(!strcmp(retr->name, "Bob"));
|
|
assert(!strcmp(retr->surname, "Miller"));
|
|
assert(retr->age == 23);
|
|
|
|
get_res = map_get(map, "b910o5");
|
|
assert(get_res.status == MAP_OK);
|
|
|
|
retr = (const Person*)get_res.value.element;
|
|
assert(!strcmp(retr->name, "Alice"));
|
|
assert(!strcmp(retr->surname, "Davis"));
|
|
assert(retr->age == 21);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
// Test map capacity tracking
|
|
void test_map_cap(void) {
|
|
map_result_t res = map_new();
|
|
|
|
assert(res.status == MAP_OK);
|
|
map_t *map = res.value.map;
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
char key[16];
|
|
snprintf(key, sizeof(key), "key%d", i);
|
|
map_add(map, key, &i);
|
|
}
|
|
|
|
assert(map_size(map) == 10);
|
|
assert(map_capacity(map) >= 10);
|
|
|
|
map_destroy(map);
|
|
}
|
|
|
|
int main(void) {
|
|
printf("=== Running Map unit tests ===\n\n");
|
|
|
|
TEST(map_new);
|
|
TEST(map_add);
|
|
TEST(map_add_multiple);
|
|
TEST(map_get);
|
|
TEST(map_get_invalid);
|
|
TEST(map_get_deleted_slots);
|
|
TEST(map_mixed);
|
|
TEST(map_update);
|
|
TEST(map_remove);
|
|
TEST(map_remove_invalid);
|
|
TEST(map_clear);
|
|
TEST(map_clear_empty);
|
|
TEST(map_sequence);
|
|
TEST(map_struct);
|
|
TEST(map_cap);
|
|
|
|
printf("\n=== All tests passed! ===\n");
|
|
|
|
return 0;
|
|
}
|