Update list implementation to work with memory pools (#168)

* Introduce indexed list implementation

* Fix CI tests for pool

* Take bitset out of pool

* Replace joker bitset interactions with wrappers

* Add bitset tests

* Add test to gitignore

---------

Co-authored-by: rfehr-idexx <ric-fehr@idexx.com>
This commit is contained in:
Rickey
2025-11-11 17:56:21 +02:00
committed by GitHub
co-authored by rfehr-idexx
parent d5f59e904b
commit e0cffab768
22 changed files with 1448 additions and 338 deletions
+16
View File
@@ -0,0 +1,16 @@
CC := gcc
CFLAGS := -I../../include -I. \
-g -O3 -Wall -Werror
SRC := bitset_test.c \
../../source/bitset.c
OUT := build/bitset_test
$(OUT): $(SRC) | build
$(CC) $(CFLAGS) -o $@ $^
build:
mkdir -p build
clean:
rm -f $(OUT)
+122
View File
@@ -0,0 +1,122 @@
#include "bitset.h"
#include "util.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
BITSET_DEFINE(test_bitset, BITSET_MAX_BITS)
// bitset_set_idx
// bitset_get_idx
// bitset_num_set_bits
// bitset_is_empty
// bitset_clear
void test_bitset_fill_all_and_empty(void)
{
assert(bitset_is_empty(&test_bitset));
for(int i = 0; i < BITSET_MAX_BITS; i++)
{
bitset_set_idx(&test_bitset, i, true);
assert(bitset_num_set_bits(&test_bitset) == (i + 1));
}
for(int i = 0; i < BITSET_MAX_BITS; i++)
{
assert(bitset_get_idx(&test_bitset, i));
}
assert(!bitset_is_empty(&test_bitset));
bitset_clear(&test_bitset);
for(int i = 0; i < BITSET_MAX_BITS; i++)
{
assert(!bitset_get_idx(&test_bitset, i));
}
assert(bitset_is_empty(&test_bitset));
}
// bitset_set_idx
// bitset_num_set_bits
// bitset_find_idx_of_nth_set
// bitset_is_empty
// bitset_clear
void test_bitset_insertions_at_boundry(void)
{
assert(bitset_is_empty(&test_bitset));
bitset_set_idx(&test_bitset, 30, true);
bitset_set_idx(&test_bitset, 31, true);
bitset_set_idx(&test_bitset, 32, true);
assert(bitset_num_set_bits(&test_bitset) == 3);
bitset_set_idx(&test_bitset, 31, false);
assert(bitset_num_set_bits(&test_bitset) == 2);
assert(bitset_find_idx_of_nth_set(&test_bitset, 0) == 30);
assert(bitset_find_idx_of_nth_set(&test_bitset, 1) == 32);
bitset_set_idx(&test_bitset, 0, true);
assert(bitset_find_idx_of_nth_set(&test_bitset, 0) == 0);
bitset_set_idx(&test_bitset, 0, false);
bitset_set_idx(&test_bitset, 0, 30);
bitset_clear(&test_bitset);
assert(bitset_is_empty(&test_bitset));
}
// bitset_set_idx
// bitset_is_empty
// bitset_clear
// bitset_itr_create
// bitset_itr_next
void test_bitset_iterator(void)
{
assert(bitset_is_empty(&test_bitset));
int test_indices[6] = {0, 31, 32, 63, 64, 100};
for(int i = 0; i < 6; i++)
{
bitset_set_idx(&test_bitset, test_indices[i], true);
}
BitsetItr itr = bitset_itr_create(&test_bitset);
int test_val = UNDEFINED;
int index = 0;
while((test_val = bitset_itr_next(&itr)) != UNDEFINED)
{
assert(test_val == test_indices[index++]);
}
assert(index == 6);
bitset_clear(&test_bitset);
assert(bitset_is_empty(&test_bitset));
}
int main(void)
{
printf("Testing Bitset Fill All and Empty.\n");
test_bitset_fill_all_and_empty();
printf("Testing Bitset Insertions At Boundry.\n");
test_bitset_insertions_at_boundry();
printf("Testing Bitset Iterator.\n");
test_bitset_iterator();
printf("-------------------------------------------------------------------------------\n");
printf("Bitset Tests Passed :)\n");
printf("-------------------------------------------------------------------------------\n");
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
CC := gcc
CFLAGS := -I../../include -I. \
-g -O3 -Wall -Werror -DPOOLS_TEST_ENV=yes
SRC := list_test.c \
../../source/list.c \
../../source/pool.c \
../../source/bitset.c
OUT := build/list_test
$(OUT): $(SRC) | build
$(CC) $(CFLAGS) -o $@ $^
build:
mkdir -p build
clean:
rm -f $(OUT)
+5
View File
@@ -0,0 +1,5 @@
#include "list.h"
#include <stddef.h>
POOL_ENTRY(ListNode, MAX_LIST_NODES);
+307
View File
@@ -0,0 +1,307 @@
#include "list.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
// As simple as it gets, just needs to be initialized correctly
// - list_create
// - list_is_empty
// - list_get_len
// - list_clear
void create_and_clear_list(void)
{
List my_cool_list = list_create();
// verify no data
assert(my_cool_list.head == NULL);
assert(my_cool_list.tail == NULL);
assert(list_get_len(&my_cool_list) == 0);
assert(list_is_empty(&my_cool_list));
list_clear(&my_cool_list);
assert(list_is_empty(&my_cool_list));
}
// Push back one entry, make sure it looks as expected
// tests:
// - list_create
// - list_push_back
// - list_is_empty
// - list_get_len
// - list_get_at_idx
// - list_clear
void push_back_one_entry(void)
{
List my_cool_list = list_create();
// verify no data
assert(my_cool_list.head == NULL);
assert(my_cool_list.tail == NULL);
assert(list_get_len(&my_cool_list) == 0);
assert(list_is_empty(&my_cool_list));
int test_data = 1337;
list_push_back(&my_cool_list, &test_data);
// should have data and be the same head/tail
assert(my_cool_list.head != NULL);
assert(my_cool_list.tail != NULL);
assert(my_cool_list.head == my_cool_list.tail);
assert(list_get_len(&my_cool_list) == 1);
// pointer should be the same
assert(my_cool_list.head->data == &test_data);
// and consequently the value should be the same
assert(*(int*)(my_cool_list.head->data) == test_data);
assert(list_get_at_idx(&my_cool_list, 0) == &test_data);
assert(list_get_at_idx(&my_cool_list, 1) == NULL);
assert(!list_is_empty(&my_cool_list));
// clear the list
list_clear(&my_cool_list);
// verify no data
assert(my_cool_list.head == NULL);
assert(my_cool_list.tail == NULL);
assert(list_is_empty(&my_cool_list));
}
// Push front one entry, make sure it looks as expected
// - list_create
// - list_push_front
// - list_is_empty
// - list_get_len
// - list_get_at_idx
// - list_clear
void push_front_one_entry(void)
{
List my_cool_list = list_create();
// verify no data
assert(my_cool_list.head == NULL);
assert(my_cool_list.tail == NULL);
assert(list_get_len(&my_cool_list) == 0);
assert(list_is_empty(&my_cool_list));
int test_data = 1337;
list_push_front(&my_cool_list, &test_data);
// should have data and be the same head/tail
assert(my_cool_list.head != NULL);
assert(my_cool_list.tail != NULL);
assert(my_cool_list.head == my_cool_list.tail);
assert(list_get_len(&my_cool_list) == 1);
assert(!list_is_empty(&my_cool_list));
// pointer should be the same
assert(my_cool_list.head->data == &test_data);
// and consequently the value should be the same
assert(*(int*)(my_cool_list.head->data) == test_data);
assert(list_get_at_idx(&my_cool_list, 0) == &test_data);
assert(list_get_at_idx(&my_cool_list, 1) == NULL);
// clear the list
list_clear(&my_cool_list);
// verify no data
assert(my_cool_list.head == NULL);
assert(my_cool_list.tail == NULL);
assert(list_is_empty(&my_cool_list));
}
// This is meant to be a full test of every function including the use of
// the ListItr iterator
//
// Push back three entries. To cover the 3 states of nodes outside of single node
// lists. 3 states being, node at the front, node at the middle, and node at the end
//
// Afterwards remove one entry with `list_remove_at_idx` and then remove another
// with `list_remove_node`
//
// Lastly, push to the front the same 3 entries and verify the state of the list
// before detroying.
//
// - list_create
// - list_push_front
// - list_is_empty
// - list_get_len
// - list_get_at_idx
// - list_remove_at_idx
// - list_itr_create
// - list_itr_next
// - list_clear
// - list_itr_remove_node_current;
void push_back_three_remove_push_front_three_entries(void)
{
List my_cool_list = list_create();
// verify no data
assert(my_cool_list.head == NULL);
assert(my_cool_list.tail == NULL);
int test_data[3] = {0, 1, 2};
list_push_back(&my_cool_list, &test_data[0]);
// Make sure with one entry it looks right
assert(my_cool_list.head == my_cool_list.tail);
list_push_back(&my_cool_list, &test_data[1]);
// Now with two it should be different
assert(my_cool_list.head != my_cool_list.tail);
list_push_back(&my_cool_list, &test_data[2]);
// 3 entries?
assert(list_get_len(&my_cool_list) == 3);
// now use an iterator to examine each node
int* data;
ListItr list_itr = list_itr_create(&my_cool_list);
int itr = 0;
ListNode* prev_ln = NULL;
while((data = list_itr_next(&list_itr)))
{
assert(data != NULL);
assert(data == &test_data[itr]);
ListNode* ln = list_itr.current_node;
switch(itr)
{
case 0:
assert(ln->prev == NULL);
assert(ln->next != NULL);
assert(ln->next->prev == ln);
break;
case 1:
assert(ln->prev == prev_ln);
assert(ln->prev != NULL);
assert(ln->next != NULL);
assert(ln->next->prev == ln);
assert(ln->prev->next == ln);
break;
case 2:
assert(ln->prev != NULL);
assert(ln->next == NULL);
assert(ln->prev->next == ln);
assert(ln->prev == prev_ln);
break;
default:
assert(false); // shouldn't get here
break;
}
itr++;
prev_ln = ln;
}
// remove the middle entry
assert(list_remove_at_idx(&my_cool_list, 1));
// can't remove entry 2 now (doesn't exist)
assert(!list_remove_at_idx(&my_cool_list, 2));
assert(list_get_len(&my_cool_list) == 2);
list_itr = list_itr_create(&my_cool_list);
data = NULL;
itr = 0;
prev_ln = NULL;
while((data = list_itr_next(&list_itr)))
{
assert(data != NULL);
ListNode* ln = list_itr.current_node;
switch(itr)
{
case 0:
assert(ln->prev == NULL);
assert(ln->next != NULL);
assert(ln->next->prev == ln);
break;
case 1:
assert(ln->prev != NULL);
assert(ln->next == NULL);
assert(ln->prev->next == ln);
assert(ln->prev == prev_ln);
list_itr_remove_current_node(&list_itr);
break;
default:
assert(false); // shouldn't get here
break;
}
itr++;
prev_ln = ln;
}
assert(list_get_len(&my_cool_list) == 1);
assert(my_cool_list.head == my_cool_list.tail);
// now push the same 3 pointers to the front of the list
list_push_front(&my_cool_list, &test_data[0]);
list_push_front(&my_cool_list, &test_data[1]);
list_push_front(&my_cool_list, &test_data[2]);
// now, the list should be in the order...
// test_data[2] -> test_data[1] -> test_data[0] -> test_data[0]
list_itr = list_itr_create(&my_cool_list);
data = NULL;
itr = 0;
while((data = list_itr_next(&list_itr)))
{
assert(data != NULL);
ListNode* ln = list_itr.current_node;
switch(itr)
{
case 0:
assert(ln->data == &test_data[2]);
break;
case 1:
assert(ln->data == &test_data[1]);
break;
case 2:
assert(ln->data == &test_data[0]);
break;
case 3:
assert(ln->data == &test_data[0]);
break;
default:
assert(false); // shouldn't get here
break;
}
itr++;
}
assert(list_get_len(&my_cool_list) == 4);
// clear the list
list_clear(&my_cool_list);
// verify no data
assert(my_cool_list.head == NULL);
assert(my_cool_list.tail == NULL);
assert(list_is_empty(&my_cool_list));
}
int main(void)
{
printf("Testing List Create and Clear.\n");
create_and_clear_list();
printf("Testing List Push Back.\n");
push_back_one_entry();
printf("Testing List Push Front.\n");
push_front_one_entry();
printf("Testing List Complete Exercise.\n");
push_back_three_remove_push_front_three_entries();
printf("-------------------------------------------------------------------------------\n");
printf("List Tests Passed :)\n");
printf("-------------------------------------------------------------------------------\n");
return 0;
}
+2 -2
View File
@@ -3,8 +3,8 @@ CC := gcc
CFLAGS := -I../../include -I. \
-g -O3 -Wall -Werror -DPOOLS_TEST_ENV=yes
SRC := pool_test.c ../../source/pool.c
OUT := build/pool_test
SRC := pool_test.c ../../source/pool.c ../../source/bitset.c
OUT := build/pool_test
$(OUT): $(SRC) | build
$(CC) $(CFLAGS) -o $@ $^
+3 -3
View File
@@ -107,9 +107,9 @@ int main(void)
printf("Testing Pool Fill and Empty.\n");
if(!test_fill_and_empty()) return UNDEFINED;
printf("---------------------------------------------------------\n");
printf("Pool Tests Passed\n");
printf("---------------------------------------------------------\n");
printf("-------------------------------------------------------------------------------\n");
printf("Pool Tests Passed :)\n");
printf("-------------------------------------------------------------------------------\n");
printf("Testing execution time for fun :)\n\n");
timestamp_t t1 = get_time();
+15 -7
View File
@@ -2,12 +2,20 @@
set -eu
run_pool_test() {
cd pool
make clean
make
./build/pool_test
cd - > /dev/null
run_test() {
name="$1"
echo "==============================================================================="
echo "Running test for: $1"
echo "==============================================================================="
cd "$name" 2>&1 > /dev/null
make clean > /dev/null
make > /dev/null
./build/"$name"_test
cd - 2>&1 > /dev/null
}
run_pool_test
run_test bitset
run_test pool
run_test list