diff --git a/Makefile b/Makefile index a540f86..7bf5838 100644 --- a/Makefile +++ b/Makefile @@ -1,78 +1,76 @@ -CC ?= gcc -AR ?= ar -LINTER ?= clang-format - -LIB := libhash - -PREFIX := /usr/local -INCDIR := $(PREFIX)/include -LIBDIR := $(PREFIX)/lib -SRCDIR := src -DEPSDIR := deps -TESTDIR := t -EXAMPLEDIR := examples -LINCDIR := include - -DYNAMIC_TARGET := $(LIB).so -STATIC_TARGET := $(LIB).a -EXAMPLE_TARGET := example -TEST_TARGET := test - -SRC := $(wildcard $(SRCDIR)/*.c) -TEST_DEPS := $(wildcard $(DEPSDIR)/tap.c/*.c) -DEPS := $(filter-out $(wildcard $(DEPSDIR)/tap.c/*), $(wildcard $(DEPSDIR)/*/*.c)) -OBJ := $(addprefix obj/, $(notdir $(SRC:.c=.o)) $(notdir $(DEPS:.c=.o))) - -CFLAGS := -I$(LINCDIR) -I$(DEPSDIR) -Wall -Wextra -pedantic -std=c17 -LIBS := -lm - -TESTS := $(wildcard $(TESTDIR)/*.c) - -SEPARATOR := --------------------------- - -all: $(DYNAMIC_TARGET) $(STATIC_TARGET) - +include Makefile.config + +.PHONY: all obj install uninstall clean unit_test unit_test_dev valgrind fmt +.DELETE_ON_ERROR: + +PREFIX := /usr/local +INCDIR := $(PREFIX)/include +LIBDIR := $(PREFIX)/lib +SRCDIR := src +DEPSDIR := deps +TESTDIR := t +EXAMPLEDIR := examples +INCDIR := include + +DYNAMIC_TARGET := $(LIBNAME).so +STATIC_TARGET := $(LIBNAME).a +EXAMPLE_TARGET := example +TEST_TARGET := test + +SRC := $(wildcard $(SRCDIR)/*.c) +TESTS := $(wildcard $(TESTDIR)/*.c) +DEPS := $(filter-out $(wildcard $(DEPSDIR)/libtap/*), $(wildcard $(DEPSDIR)/*/*.c)) +TEST_DEPS := $(wildcard $(DEPSDIR)/libtap/*.c) +OBJ := $(addprefix obj/, $(notdir $(SRC:.c=.o)) $(notdir $(DEPS:.c=.o))) + +INCLUDES := -I$(INCDIR) -I$(DEPSDIR) -I$(SRCDIR) +LIBS := -lm +CFLAGS := -Wall -Wextra -pedantic -std=c17 $(INCLUDES) + +$(DYNAMIC_TARGET): CFLAGS += -shared $(DYNAMIC_TARGET): $(OBJ) - $(CC) $(CFLAGS) $(OBJ) -shared $(LIBS) -o $(DYNAMIC_TARGET) + $(CC) $(CFLAGS) $^ $(LIBS) -o $@ $(STATIC_TARGET): $(OBJ) - $(AR) rcs $@ $(OBJ) + $(AR) rcs $@ $^ -obj/%.o: $(SRCDIR)/%.c $(LINCDIR)/$(LIB).h | obj +obj/%.o: $(SRCDIR)/%.c $(INCDIR)/$(LIBNAME).h | obj $(CC) $< -c $(CFLAGS) -o $@ obj/%.o: $(DEPSDIR)/*/%.c | obj $(CC) $< -c $(CFLAGS) -o $@ +$(EXAMPLE_TARGET): $(STATIC_TARGET) + $(CC) $(CFLAGS) $(EXAMPLEDIR)/main.c $< $(LIBS) -o $@ + +all: $(DYNAMIC_TARGET) $(STATIC_TARGET) + obj: - mkdir -p obj + @mkdir -p obj install: $(STATIC_TARGET) - mkdir -p ${LIBDIR} && cp -f ${STATIC_TARGET} ${LIBDIR}/$(STATIC_TARGET) - mkdir -p ${INCDIR} && cp -r $(LINCDIR)/$(LIB).h ${INCDIR} + @mkdir -p ${LIBDIR} && cp -f ${STATIC_TARGET} ${LIBDIR}/$@ + @mkdir -p ${INCDIR} && cp -r $(INCDIR)/$(LIBNAME).h ${INCDIR} uninstall: - rm -f ${LIBDIR}/$(STATIC_TARGET) - rm -f ${INCDIR}/libys.h - -$(EXAMPLE_TARGET): $(STATIC_TARGET) - $(CC) $(CFLAGS) $(EXAMPLEDIR)/main.c $(STATIC_TARGET) $(LIBS) -o $(EXAMPLE_TARGET) + @rm -f ${LIBDIR}/$(STATIC_TARGET) + @rm -f ${INCDIR}/libys.h clean: - rm -f $(OBJ) $(STATIC_TARGET) $(DYNAMIC_TARGET) $(EXAMPLE_TARGET) $(TEST_TARGET) + @rm -f $(OBJ) $(STATIC_TARGET) $(DYNAMIC_TARGET) $(EXAMPLE_TARGET) $(TEST_TARGET) -test: $(STATIC_TARGET) - $(foreach test,$(TESTS), \ - $(MAKE) .compile_test file=$(test); \ - printf "\033[1;32m\nRunning test $(patsubst $(TESTDIR)/%,%,$(test))...\n$(SEPARATOR)\n\033[0m"; \ - ./test;\ - ) - rm $(TEST_TARGET) +unit_test: $(STATIC_TARGET) + $(CC) $(CFLAGS) $(TESTS) $(TEST_DEPS) $(STATIC_TARGET) -I$(SRCDIR) $(LIBS) -o $(TEST_TARGET) + ./$(TEST_TARGET) + $(MAKE) clean -.compile_test: - $(CC) $(CFLAGS) $(file) $(TEST_DEPS) $(STATIC_TARGET) -I$(SRCDIR) -I$(DEPSDIR) $(LIBS) -o $(TEST_TARGET) +unit_test_dev: + ls $(SRCDIR)/*.{h,c} $(TESTDIR)/*.{h,c} | entr -s 'make -s unit_test' -lint: - $(LINTER) -i $(wildcard $(SRCDIR)/*) $(wildcard $(TESTDIR)/*) $(wildcard $(LINCDIR)/*) $(wildcard $(EXAMPLEDIR)/*) +valgrind: $(STATIC_TARGET) + $(CC) $(CFLAGS) $(TESTS) $(TEST_DEPS) $< $(LIBS) -o $(TEST_TARGET) + $(VALGRIND) --leak-check=full --track-origins=yes -s ./$(TEST_TARGET) + @$(MAKE) clean -.PHONY: clean test .compile_test all obj install uninstall lint +fmt: + @$(FMT) -i $(wildcard $(SRCDIR)/*) $(wildcard $(TESTDIR)/*) $(wildcard $(INCDIR)/*) $(wildcard $(EXAMPLEDIR)/*) diff --git a/Makefile.config b/Makefile.config new file mode 100644 index 0000000..1f512dc --- /dev/null +++ b/Makefile.config @@ -0,0 +1,6 @@ +CC ?= gcc +AR ?= ar +FMT ?= clang-format +VALGRIND ?= valgrind + +LIBNAME := libhash diff --git a/include/libhash.h b/include/libhash.h index 41c6566..6915329 100644 --- a/include/libhash.h +++ b/include/libhash.h @@ -1,8 +1,10 @@ #ifndef LIBHASH_H #define LIBHASH_H -#define HT_DEFAULT_CAPACITY 50 -#define HS_DEFAULT_CAPACITY 50 +#include "list.h" + +#define HT_DEFAULT_CAPACITY 53 +#define HS_DEFAULT_CAPACITY 53 /** * A free function that will be invoked a hashmap value any time it is removed. @@ -51,6 +53,8 @@ typedef struct { * however they want. */ free_fn *free_value; + + node_t *occupied_buckets; } hash_table; /** @@ -108,6 +112,15 @@ void ht_delete_table(hash_table *ht); */ int ht_delete(hash_table *ht, const char *key); +#define HT_ITER_START(ht) \ + node_t *head = ht->occupied_buckets; \ + while (head != &LIST_SENTINEL_NODE) { \ + ht_entry *entry = ht->entries[head->value]; + +#define HT_ITER_END \ + head = head->next; \ + } + typedef struct { /** * Max number of keys which may be stored in the hash set. Adjustable. diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index b4e8026..f93009e 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/sh -l -ret="$(make -s test 2>/dev/null)" +ret="$(make -s unit_test 2>/dev/null)" echo "$ret" diff --git a/src/hash.c b/src/hash.c index 5139830..5d06437 100644 --- a/src/hash.c +++ b/src/hash.c @@ -46,7 +46,9 @@ static int h_hash(const char *key, const int prime, const int capacity) { */ int h_resolve_hash(const char *key, const int capacity, const int attempt) { const int hash_a = h_hash(key, H_PRIME_1, capacity); - const int hash_b = h_hash(key, H_PRIME_2, capacity); + int hash_b = h_hash(key, H_PRIME_2, capacity); - return (hash_a + attempt * (hash_b == 0 ? 1 : hash_b)) % capacity; + // Prevent infinite cycling when hash_b == num buckets. + if (hash_b % capacity == 0) hash_b = 1; + return (hash_a + (attempt * hash_b)) % capacity; } diff --git a/src/hash_table.c b/src/hash_table.c index f4b92b6..55fe190 100644 --- a/src/hash_table.c +++ b/src/hash_table.c @@ -25,16 +25,15 @@ static void __ht_delete_table(hash_table *ht); * @param base_capacity * @return int */ -static void ht_resize(hash_table *ht, const int base_capacity) { - if (base_capacity < 0) { - return; +static void ht_resize(hash_table *ht, int base_capacity) { + if (base_capacity < HT_DEFAULT_CAPACITY) { + base_capacity = HT_DEFAULT_CAPACITY; } hash_table *new_ht = ht_init(base_capacity, ht->free_value); for (unsigned int i = 0; i < ht->capacity; i++) { ht_entry *r = ht->entries[i]; - if (r != NULL && r != &HT_SENTINEL_ENTRY) { __ht_insert(new_ht, r->key, r->value); } @@ -44,15 +43,17 @@ static void ht_resize(hash_table *ht, const int base_capacity) { ht->count = new_ht->count; const int tmp_capacity = ht->capacity; - ht->capacity = new_ht->capacity; new_ht->capacity = tmp_capacity; ht_entry **tmp_entries = ht->entries; ht->entries = new_ht->entries; new_ht->entries = tmp_entries; + ht->occupied_buckets = new_ht->occupied_buckets; - ht_delete_table(new_ht); + // Cannot free values - we may still be using them. + free(new_ht->entries); + free(new_ht); } /** @@ -121,34 +122,34 @@ static void __ht_insert(hash_table *ht, const char *key, void *value) { ht_entry *new_entry = ht_entry_init(key, value); int idx = h_resolve_hash(new_entry->key, ht->capacity, 0); - ht_entry *current_entry = ht->entries[idx]; + // If there was a hash collision, we need to perform double hashing and + // partial linear probing by incrementing this index and hashing it until we + // find a bucket. int i = 1; - - // i.e. if there was a collision while (current_entry != NULL && current_entry != &HT_SENTINEL_ENTRY) { - // update existing key/value + // If the keys match, then we've inserted this key before. Use this bucket. if (strcmp(current_entry->key, key) == 0) { - ht_delete_entry(current_entry, ht->free_value); + ht_delete_entry(current_entry, NULL); ht->entries[idx] = new_entry; - return; } - // TODO verify i is 1.. idx = h_resolve_hash(new_entry->key, ht->capacity, i); current_entry = ht->entries[idx]; i++; } ht->entries[idx] = new_entry; + list_prepend(&ht->occupied_buckets, idx); ht->count++; } static int __ht_delete(hash_table *ht, const char *key) { const int load = ht->count * 100 / ht->capacity; - if (load < 10) { + // TODO: const + if (load < 30) { ht_resize_down(ht); } @@ -156,12 +157,11 @@ static int __ht_delete(hash_table *ht, const char *key) { int idx = h_resolve_hash(key, ht->capacity, i); ht_entry *current_entry = ht->entries[idx]; - while (current_entry != NULL && current_entry != &HT_SENTINEL_ENTRY) { if (strcmp(current_entry->key, key) == 0) { ht_delete_entry(current_entry, ht->free_value); ht->entries[idx] = &HT_SENTINEL_ENTRY; - + list_remove(&ht->occupied_buckets, idx); ht->count--; return 1; @@ -199,7 +199,7 @@ hash_table *ht_init(int base_capacity, free_fn *free_value) { ht->count = 0; ht->entries = calloc((size_t)ht->capacity, sizeof(ht_entry *)); ht->free_value = free_value; - + ht->occupied_buckets = list_node_create_head(); return ht; } diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..139ea06 --- /dev/null +++ b/src/list.h @@ -0,0 +1,62 @@ +#ifndef LIBHASH_LIST_H +#define LIBHASH_LIST_H + +#include +#include + +typedef struct node node_t; +struct node { + int value; + node_t *next; +}; + +static node_t LIST_SENTINEL_NODE = {0, NULL}; + +static node_t *list_node_create_head(void) { return &LIST_SENTINEL_NODE; } + +static node_t *list_node_create(const int value) { + node_t *n = (node_t *)malloc(sizeof(node_t)); + n->value = value; + return n; +} + +static void list_prepend(node_t **head, int value) { + // TODO: xmalloc + node_t *new_node = (node_t *)malloc(sizeof(node_t)); + new_node->value = value; + + node_t *tmp = *head; + *head = new_node; + new_node->next = tmp; +} + +static void list_remove(node_t **head, int value) { + node_t *current = *head; + node_t *prev = NULL; + + while (current != NULL) { + if (current->value == value) { + if (prev == NULL) { + *head = current->next; + } else { + prev->next = current->next; + } + free(current); + return; + } + prev = current; + current = current->next; + } +} + +// TODO: Use and test +static void free_list(node_t *head) { + node_t *tmp; + while (head) { + tmp = head; + head = head->next; + free(tmp); + } +} + +#endif /* LIBHASH_LIST_H */ diff --git a/src/prime.h b/src/prime.h index f67167e..c0a2e15 100644 --- a/src/prime.h +++ b/src/prime.h @@ -1,7 +1,7 @@ -#ifndef PRIME_H -#define PRIME_H +#ifndef LIBHASH_PRIME_H +#define LIBHASH_PRIME_H int is_prime(const int x); int next_prime(int x); -#endif /* PRIME_H */ +#endif /* LIBHASH_PRIME_H */ diff --git a/t/hash_set_test.c b/t/hash_set_test.c index a839ed7..b55a1ec 100644 --- a/t/hash_set_test.c +++ b/t/hash_set_test.c @@ -2,10 +2,10 @@ #include #include "libhash.h" -#include "libtap/libtap.h" #include "prime.h" +#include "tests.h" -void test_initialization(void) { +static void test_initialization(void) { int capacity = 20; char *k = "key"; @@ -21,7 +21,7 @@ void test_initialization(void) { lives({ hs_delete_set(hs); }, "frees the hash set heap memory"); } -void test_insert(void) { +static void test_insert(void) { hash_set *hs = hs_init(10); const char *k1 = "k1"; const char *k2 = "k2"; @@ -48,7 +48,7 @@ void test_insert(void) { ok(hs_contains(hs, k1), "inserts the key clean after having been deleted"); } -void test_contains(void) { +static void test_contains(void) { hash_set *hs = hs_init(10); const char *k1 = "k1"; const char *k2 = "k2"; @@ -69,7 +69,7 @@ void test_contains(void) { ok(hs_contains(hs, k1) == 0, "returns 0 if the entry was deleted"); } -void test_delete(void) { +static void test_delete(void) { const char *k1 = "k1"; const char *k2 = "k2"; const char *k3 = "k3"; @@ -97,7 +97,7 @@ void test_delete(void) { "returns 0 because the entry has been deleted"); } -void test_capacity(void) { +static void test_capacity(void) { int initial_cap = 23; hash_set *hs = hs_init(initial_cap); @@ -116,7 +116,7 @@ void test_capacity(void) { ok(hs->count == initial_cap, "maintains the count"); } -void test_contains_miss(void) { +static void test_contains_miss(void) { hash_set *hs = hs_init(2); hs_insert(hs, "Content-Type"); @@ -125,15 +125,11 @@ void test_contains_miss(void) { ok(hs_contains(hs, "key2") == 0, "does not contain the key"); } -int main() { - plan(31); - +void run_hash_set_tests(void) { test_initialization(); test_insert(); test_contains(); test_delete(); test_capacity(); test_contains_miss(); - - done_testing(); } diff --git a/t/hash_table_test.c b/t/hash_table_test.c index b9f6f47..8dd6283 100644 --- a/t/hash_table_test.c +++ b/t/hash_table_test.c @@ -1,13 +1,23 @@ #include #include -#include "libtap/libtap.h" +#include "tests.h" // include the entire source so we may test static functions // without conditional compilation #include "hash_table.c" -void test_initialization(void) { +static hash_table *init_test_ht(void) { + hash_table *ht = ht_init(10, NULL); + + ht_insert(ht, "k1", "v1"); + ht_insert(ht, "k2", "v2"); + ht_insert(ht, "k3", "v3"); + + return ht; +} + +static void test_ht_initialization(void) { int capacity = 20; char *k = "key"; char *v = "value"; @@ -30,7 +40,7 @@ void test_initialization(void) { lives({ ht_delete_entry(r, false); }, "frees the entry heap memory"); } -void test_insert(void) { +static void test_ht_insert(void) { hash_table *ht = ht_init(10, NULL); ht_insert(ht, "k1", "v1"); @@ -57,12 +67,8 @@ void test_insert(void) { "inserts the entry clean after having been deleted"); } -void test_search(void) { - hash_table *ht = ht_init(10, NULL); - - ht_insert(ht, "k1", "v1"); - ht_insert(ht, "k2", "v2"); - ht_insert(ht, "k3", "v3"); +static void test_ht_search(void) { + hash_table *ht = init_test_ht(); is(ht_search(ht, "k1")->value, "v1", "retrieves the value"); // `ht_get` is essentially a wrapper for `ht_search` @@ -75,12 +81,8 @@ void test_search(void) { is(ht_get(ht, "k1"), NULL, "returns NULL if the entry was deleted"); } -void test_delete(void) { - hash_table *ht = ht_init(10, NULL); - - ht_insert(ht, "k1", "v1"); - ht_insert(ht, "k2", "v2"); - ht_insert(ht, "k3", "v3"); +static void test_ht_delete(void) { + hash_table *ht = init_test_ht(); ok(ht_delete(ht, "k1") == 1, "returns 1 when entry deletion was successful"); ok(ht_delete(ht, "k2") == 1, "returns 1 when entry deletion was successful"); @@ -88,6 +90,7 @@ void test_delete(void) { ok(ht_delete(ht, "k4") == 0, "returns 0 when entry deletion was unsuccessful"); + ok(ht_delete(ht, "k1") == 0, "cannot delete the same entry twice"); is(ht_get(ht, "k1"), NULL, "returns NULL because the entry has been deleted"); @@ -95,7 +98,7 @@ void test_delete(void) { is(ht_get(ht, "k3"), NULL, "returns NULL because the entry has been deleted"); } -void test_capacity(void) { +static void test_ht_capacity(void) { int initial_cap = 23; hash_table *ht = ht_init(initial_cap, NULL); @@ -108,14 +111,12 @@ void test_capacity(void) { ht_insert(ht, buf, "x"); } - ok(ht->base_capacity == 46, "extends the base capacity"); - ok(ht->capacity == next_prime(initial_cap * 2), - "extends the actual capacity"); + ok(ht->base_capacity == HT_DEFAULT_CAPACITY, "extends the base capacity"); + ok(ht->capacity == HT_DEFAULT_CAPACITY, "extends the actual capacity"); ok(ht->count == initial_cap, "maintains the count"); } -void *free(void *value) { free((char *)value); } -void test_delete_with_free(void) { +static void test_ht_delete_with_free(void) { hash_table *ht = ht_init(10, free); char *v1 = strdup("v1"); @@ -127,11 +128,11 @@ void test_delete_with_free(void) { ht_insert(ht, "k3", v3); ok(ht_delete(ht, "k1") == 1, "returns 1 when entry deletion was successful"); - ok(v1 == NULL, "passes the value to the provided free function"); + ok(strcmp(v1, "v1") != 0, "passes the value to the provided free function"); ok(ht_delete(ht, "k2") == 1, "returns 1 when entry deletion was successful"); - ok(v2 == NULL, "passes the value to the provided free function"); + ok(strcmp(v2, "v2") != 0, "passes the value to the provided free function"); ok(ht_delete(ht, "k3") == 1, "returns 1 when entry deletion was successful"); - ok(v3 == NULL, "passes the value to the provided free function"); + ok(strcmp(v3, "v3") != 0, "passes the value to the provided free function"); ok(ht_delete(ht, "k4") == 0, "returns 0 when entry deletion was unsuccessful"); @@ -142,20 +143,41 @@ void test_delete_with_free(void) { is(ht_get(ht, "k3"), NULL, "returns NULL because the entry has been deleted"); // It'll be some junk value - just not the og value. - isnt(v1, "v1", "frees the entry value"); - isnt(v2, "v2", "frees the entry value"); - isnt(v3, "v3", "frees the entry value"); + ok(strcmp(v1, "v1") != 0, "frees the entry value"); + ok(strcmp(v2, "v2") != 0, "frees the entry value"); + ok(strcmp(v3, "v3") != 0, "frees the entry value"); } -int main() { - plan(44); - - test_initialization(); - test_insert(); - test_search(); - test_delete(); - test_capacity(); - test_delete_with_free(); +static void test_ht_iterate(void) { + hash_table *ht = init_test_ht(); + ht_delete(ht, "k2"); + ht_insert(ht, "k4", "v4"); + + unsigned int count = 0; + HT_ITER_START(ht) + switch (count++) { + case 0: + is(entry->value, "v4", "most recent entry at head"); + break; + case 1: + is(entry->value, "v3", "retains entry"); + break; + case 2: + is(entry->value, "v1", "first entry at tail"); + break; + case 3: + is(entry->value, NULL, "terminates where expected"); + break; + } + HT_ITER_END +} - done_testing(); +void run_hash_table_tests(void) { + test_ht_initialization(); + test_ht_insert(); + test_ht_search(); + test_ht_delete(); + test_ht_capacity(); + test_ht_delete_with_free(); + test_ht_iterate(); } diff --git a/t/list_test.c b/t/list_test.c new file mode 100644 index 0000000..b78d609 --- /dev/null +++ b/t/list_test.c @@ -0,0 +1,37 @@ +#include "list.h" + +#include + +#include "tests.h" + +static void test_list_prepend(void) { + node_t *head = list_node_create(100); + + list_prepend(&head, 200); + + ok(head->next->value == 100, "tail node is now 100"); + ok(head->value == 200, "head node is now 200"); +} + +static void test_list_remove(void) { + node_t *head = list_node_create(100); + list_prepend(&head, 200); + list_prepend(&head, 300); + list_prepend(&head, 400); + + list_remove(&head, 300); + + int n1 = head->value; + int n2 = head->next->value; + int n3 = head->next->next->value; + + ok(n1 == 400, "expected value"); + ok(n2 == 200, "expected value"); + ok(n3 == 100, "expected value"); + ok(head->next->next->next == NULL, "no more nodes"); +} + +void run_list_tests() { + test_list_prepend(); + test_list_remove(); +} diff --git a/t/main.c b/t/main.c new file mode 100644 index 0000000..bdf22b8 --- /dev/null +++ b/t/main.c @@ -0,0 +1,13 @@ +#include "libtap/libtap.h" +#include "tests.h" + +int main(void) { + plan(135); + + run_hash_set_tests(); + run_hash_table_tests(); + run_prime_tests(); + run_list_tests(); + + done_testing(); +} diff --git a/t/prime_test.c b/t/prime_test.c index 1d740c9..d128073 100644 --- a/t/prime_test.c +++ b/t/prime_test.c @@ -1,12 +1,12 @@ #include "prime.h" -#include "libtap/libtap.h" +#include "tests.h" static const int prime_map[] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97}; -void test_is_prime(void) { +static void test_is_prime(void) { for (unsigned int i = 0; i < sizeof(prime_map) / sizeof(int); i++) { int prime = prime_map[i]; @@ -14,7 +14,7 @@ void test_is_prime(void) { } } -void test_next_prime(void) { +static void test_next_prime(void) { for (unsigned int i = 2; i < sizeof(prime_map) / sizeof(int); i++) { int prime = prime_map[i]; @@ -22,13 +22,7 @@ void test_next_prime(void) { } } -int main() { - plan(48); - +void run_prime_tests(void) { test_is_prime(); test_next_prime(); - - done_testing(); - - return 0; } diff --git a/t/tests.h b/t/tests.h new file mode 100644 index 0000000..b9a5576 --- /dev/null +++ b/t/tests.h @@ -0,0 +1,11 @@ +#ifndef TESTS_H +#define TESTS_H + +#include "libtap/libtap.h" + +void run_hash_set_tests(void); +void run_hash_table_tests(void); +void run_prime_tests(void); +void run_list_tests(void); + +#endif /* TESTS_H */