From ed41c042090c88749a4b439b3bbd788276cb03e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorben=20H=C3=B6hne?= Date: Wed, 25 Jun 2025 23:17:02 +0000 Subject: [PATCH] feature (#1) This feature adds the following functions: - set_malloc() - set_free() Which is a safe malloc wrapper, that frees all memory after a test has finished. Reviewed-on: https://gitea.thoehne.com/thoehne/Small-Enough-Tester/pulls/1 --- README.md | 10 +- cpp_test.cpp | 8 -- include/list.h | 33 ++++++ set.h => include/set.h | 145 +++++++++++++++++++++++-- set_asserts.h => include/set_asserts.h | 0 include/utils.h | 54 +++++++++ src/list.c | 44 ++++++++ set.c => src/set.c | 37 +------ src/utils.c | 78 +++++++++++++ testtest.c | 38 +++++-- 10 files changed, 377 insertions(+), 70 deletions(-) delete mode 100644 cpp_test.cpp create mode 100644 include/list.h rename set.h => include/set.h (56%) rename set_asserts.h => include/set_asserts.h (100%) create mode 100644 include/utils.h create mode 100644 src/list.c rename set.c => src/set.c (86%) create mode 100644 src/utils.c diff --git a/README.md b/README.md index 650fea6..0fde13c 100644 --- a/README.md +++ b/README.md @@ -2,15 +2,11 @@ Tiny C testing framework. -## Build +## Building a test. -Building is quite simple. To build a test `test_fac.c` just pick your favourite compiler and run. +To build a test, first build the library. Then link it to the `test.c` via: - gcc Small-Enough-Tester/set.c test_fac.c -I Small-Enough-Tester/ -D COLORIZED -lm -o test_fac - -Running a test is equally simple just execute the compiled binary. - - ./test_fac + gcc src/*.c test.c -o test ## LICENSE diff --git a/cpp_test.cpp b/cpp_test.cpp deleted file mode 100644 index a37207e..0000000 --- a/cpp_test.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "set.h" -#include "set_asserts.h" - -TEST(Basic) { ASSERT_EQ(2, 2); } - -SUIT(Basic) { ADD_TEST(Basic); } - -BUNDLE() { ADD_SUIT(Basic); } diff --git a/include/list.h b/include/list.h new file mode 100644 index 0000000..eb2605d --- /dev/null +++ b/include/list.h @@ -0,0 +1,33 @@ +#include + +struct SETBlockMeta +{ + struct SETBlockMeta *next; + struct SETBlockMeta *prev; + struct SETBlockMeta *end; +}; + +/** + * Adds a new element to the list. The MetaData should sit at the start + * of a block. + * + * head (SETBlockMeta) List head. + * next (SETBlockMeta) Next meta to append. + */ +void set_ll_append(struct SETBlockMeta *head, struct SETBlockMeta *next); + +/** + * Frees all blocks in the list. + * + * head (SETBlockMeta) List head. + */ +void set_ll_free_all(struct SETBlockMeta *head); + +/** + * Frees the block with the given address and returns the new head. + * + * head (SETBlockMeta) List head. + * address (void*) Address to free. + */ + +struct SETBlockMeta *set_ll_free_one(struct SETBlockMeta *head, void *address); diff --git a/set.h b/include/set.h similarity index 56% rename from set.h rename to include/set.h index bd473db..fe4a1f5 100644 --- a/set.h +++ b/include/set.h @@ -2,17 +2,23 @@ #define _GNU_SOURCE #include -#include #include #include #include +#include "utils.h" + #ifndef INCLUDE_SET_H #define INCLUDE_SET_H #define SET_MAX_ERROR_MSG_SIZE 256 #define SET_MAX_NAME_SIZE 64 +/** + * Meta data for one test function. + * + * See also: struct SETSuit + */ struct SETest { void (*function)(struct SETest *test); @@ -21,6 +27,11 @@ struct SETest bool passed; }; +/** + * Meta data for one test suit. + * + * See also: struct SETest + */ struct SETSuit { const char *name; @@ -32,26 +43,98 @@ struct SETSuit bool passed; }; -char *format_string(const char *fmt, ...); - -// To be implemented by user. +/** + * Internal function header for bundle. Use BUNDLE() macro to define bundle in + * test. + */ void set_bundle_suits(struct SETSuit **suits, int *counter, bool count); -int create_shared_suit_space(size_t size); - +/** + * Empty suit_setup. Resolving EMPTY in a suit constructor gets + * us here. + * + * Note: Use EMPTY instead of this function. + */ static bool EMPTY_suit_setup() { return true; } +/** + * Empty suit_tear_down. Resolving EMPTY in a suit constructor gets + * us here. + * + * Note: Use EMPTY instead of this function. + */ static bool EMPTY_suit_tear_down() { return true; } +/** + * Internal header for global setup. Use SETUP() macro instead. + */ bool set_up(); + +/** + * Internal header for global tear down. Use TEAR_DOWN() macro instead. + */ bool tear_down(); +/** + * Global setup function. + * Gets called at the start of the whole test program. + * + * Should return True if setup was successfull, false if not. + * Tests are not executed on false. + * + * Note: This function has to be set in the tests even if it is not used. + * + * See also: NO_SETUP() + */ #define SETUP() bool set_up() + +/** + * Empty setup function returning true. + */ +#define NO_SETUP \ + bool set_up() { return true; } + +/** + * Setup function that allways fails. May be usefull to set if tests shouldn't + * be executed. + */ +#define DEACTIVATE \ + bool set_up() \ + { \ + printf("All tests are deactivated.\n"); \ + return false; \ + } + +/** + * Global tear down function. + * Gets called at the end of the while test program. + * + * Should return true if tear down was successfull, false if not. + * + * Note: This function has to be set in the tests even if it is not used. + */ #define TEAR_DOWN() bool tear_down() +/** + * Empty tear down function returning true. + */ +#define NO_TEAR_DOWN \ + bool tear_down() { return true; } + +/** + * Section in the tests to register all suits for execution. + * + * See also: ADD_SUIT() + * See also: SUIT, SUIT_ST + */ #define BUNDLE() \ void set_bundle_suits(struct SETSuit **suits, int *counter, bool count) +/** + * Used in BUNDLE() to register suit. + * + * suit_name (Parameter) Name of the test suit to add. + */ #define ADD_SUIT(suit_name) \ if (!count) \ { \ @@ -59,8 +142,20 @@ bool tear_down(); } \ (*counter)++; -// We manually allocate suits via mmap be able to mark them DONTFORK. -// Since we don't need the suits in a test fork. +/** + * Creates a new test suit with local setup and tear_down function. + * + * suit_name (Parameter) Name of the suit. (Must be globally unique for suits.) + * setup_func (SUIT_SETUP) Suit setup function name. (Write name EMPTY if no + * setup is required.) tear_down_func (SUIT_TEAR_DOWN) Suit tear down function + * name. (Write name EMPTY if no tear down is required) + * + * See also: SUIT_SETUP() + * See also: SUIT_TEAR_DOWN() + * + * Technical Note: We manually allocate suits via mmap to be able to mark them + * DONTFORK. Since we don't need the suits in a test fork. + */ #define SUIT_ST(suit_name, setup_func, tear_down_func) \ void suit_name##_suit(struct SETSuit *suit, bool count); \ struct SETSuit *suit_name##_suit_contructor() \ @@ -89,12 +184,38 @@ bool tear_down(); } \ void suit_name##_suit(struct SETSuit *suit, bool count) +/** + * Suit local setup function. If registered in suit it get's executed before the + * suit starts the first test. + * + * suit_name (Parameter) Name of the suit setup function. (Must be globally + * unique for suit setup functions!) + */ #define SUIT_SETUP(suit_name) bool suit_name##_suit_setup() +/** + * Suit local tear down function. If registered in suit it get's executed after + * the last test was executed. + * + * suit_name (Parameter) Name of the suit setup function. (Must be globally + * unique for suit tear down functions!) + */ #define SUIT_TEAR_DOWN(suit_name) bool suit_name##_suit_tear_down() +/** + * Suit without setup or tear down. + * + * suit_name (Parameter) Name of the suit. (Must be globally unique for suits!) + */ #define SUIT(suit_name) SUIT_ST(suit_name, EMPTY, EMPTY) +/** + * Registers a test with a test suit. + * + * test_name (Parameter) Name of the test. + * + * See also: TEST() + */ #define ADD_TEST(test_name) \ if (!count) \ { \ @@ -102,7 +223,13 @@ bool tear_down(); } \ suit->len++; -// TEST MACRO +/** + * Test function. If registered in suit it gets executed be that suit. + * + * test_name (Parameter) Name of the test (Must be globally unique for tests!) + * + * See also: set_asserts.h + */ #define TEST(test_name) \ void test_name##_test(struct SETest *test); \ void test_name##_test_constructor(struct SETest *test) \ diff --git a/set_asserts.h b/include/set_asserts.h similarity index 100% rename from set_asserts.h rename to include/set_asserts.h diff --git a/include/utils.h b/include/utils.h new file mode 100644 index 0000000..36613a3 --- /dev/null +++ b/include/utils.h @@ -0,0 +1,54 @@ +#include + +/* + * Returns pointer to formatted the string. + * + * fmt (const char*) Formatter string. + * ... (args) Arguments. + * + * Note: The string is dynamically allocated and must be freed. + */ +char *format_string(const char *fmt, ...); + +/** + * Internal function to create shared memory for all tests in a suit. + * + * size (size_t) Size of space in bytes. + */ +int create_shared_suit_space(size_t size); + +/** + * Wrapper for malloc function that notes down the allocated blocks. All + * blocks allocated with this function get freed after test execution. + * This function should only be used if the object might still be in memory + * once a test fails. Manual allocation should be preferred. + * + * n (size_t) Amount of bytes to allocate. + */ +void *set_malloc(size_t n); + +/** + * Wrapper for fee(void*). + * + * See also: set_malloc() + */ +void set_free(void *address); + +/** + * Internal function to free all allocated and registered memory. + */ +void set_free_all(); + +/** + * Wrapper for calloc. + * + * See also: set_malloc() + */ +void *set_calloc(size_t n, size_t size); + +/** + * Wrapper for realloc(). + * + * See also: set_malloc() + */ +void *set_realloc(size_t n); diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..e182b59 --- /dev/null +++ b/src/list.c @@ -0,0 +1,44 @@ +#include "list.h" + +#include + +void set_ll_append(struct SETBlockMeta *head, struct SETBlockMeta *next) +{ + while (head->next) + head = head->next; + + head->next = next; + next->prev = head; +} + +void set_ll_free_all(struct SETBlockMeta *head) +{ + while (head) + { + void *block_start = head; + head = head->next; + free(block_start); + } +} + +struct SETBlockMeta *set_ll_free_one(struct SETBlockMeta *head, void *address) +{ + struct SETBlockMeta *meta = address - (sizeof(struct SETBlockMeta)); + + if (meta == head) + { + struct SETBlockMeta *ret = meta->next; + ret->prev = NULL; + ret->end = meta->end; + free(meta); + return ret; + } + + meta->prev->next = meta->next; + if (meta->next) + meta->next->prev = meta->prev; + + free(meta); + + return head; +} diff --git a/set.c b/src/set.c similarity index 86% rename from set.c rename to src/set.c index 335f65f..7d941ca 100644 --- a/set.c +++ b/src/set.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -56,41 +55,6 @@ "" //clang-format on -char *format_string(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - size_t size = vsnprintf(NULL, 0, fmt, args); - char *out = (char *)malloc(size + 1); - - if (!out) - { - fprintf(stderr, "%s", "Failed to allocate for format string.\n"); - } - - va_end(args); - - va_start(args, fmt); - vsnprintf(out, size + 1, fmt, args); - va_end(args); - - return out; -} - -int create_shared_suit_space(size_t size) -{ - int suit_space_id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600); - - if (suit_space_id == -1) - { - fprintf(stdout, "Couldn't create suite space of size: %lu\n", size); - perror("shmget"); - exit(1); - } - - return suit_space_id; -} - static void log_test_summary(struct SETest *test) { @@ -122,6 +86,7 @@ static void dispatch_single_test(struct SETest *test) { test->function(test); log_test_summary(test); + set_free_all(); exit(0); } } diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..1a693a5 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,78 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include "list.h" +#include + +struct SETBlockMeta *block_meta_head = NULL; + +char *format_string(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + size_t size = vsnprintf(NULL, 0, fmt, args); + char *out = (char *)malloc(size + 1); + + if (!out) + { + fprintf(stderr, "%s", "Failed to allocate for format string.\n"); + } + + va_end(args); + + va_start(args, fmt); + vsnprintf(out, size + 1, fmt, args); + va_end(args); + + return out; +} + +int create_shared_suit_space(size_t size) +{ + int suit_space_id = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600); + + if (suit_space_id == -1) + { + fprintf(stdout, "Couldn't create suite space of size: %lu\n", size); + perror("shmget"); + exit(1); + } + + return suit_space_id; +} + +void *set_malloc(size_t n) +{ + void *blocks = malloc(n + sizeof(struct SETBlockMeta)); + struct SETBlockMeta *meta = blocks; + meta->next = NULL; + + if (block_meta_head == NULL) + { + meta->end = meta; + block_meta_head = meta; + } + else + { + meta->end = NULL; + set_ll_append(block_meta_head->end, meta); + block_meta_head->end = meta; + } + + return blocks + sizeof(struct SETBlockMeta); +} + +void set_free(void *address) +{ + block_meta_head = set_ll_free_one(block_meta_head, address); +} + +void set_free_all() +{ + set_ll_free_all(block_meta_head); + block_meta_head = NULL; +} diff --git a/testtest.c b/testtest.c index 88dc647..3d875c8 100644 --- a/testtest.c +++ b/testtest.c @@ -1,5 +1,6 @@ #include "set.h" #include +#include #include #include "set_asserts.h" @@ -18,12 +19,7 @@ SETUP() printf("Setting up.\n"); return true; } - -TEAR_DOWN() -{ - printf("Ending execution."); - return true; -} +NO_TEAR_DOWN TEST(Faculty_Basic) { @@ -38,19 +34,19 @@ TEST(Faculty_Negative) ASSERT_EQ(fac(0), 1); } -SUIT_SETUP(Basic) +SUIT_SETUP(Basic_Setup) { fprintf(stdout, "Some suit setup.\n"); return true; } -SUIT_TEAR_DOWN(Basic) +SUIT_TEAR_DOWN(Basic_Tear_Down) { fprintf(stdout, "Some suit teardown\n"); return true; } -SUIT_ST(Basic, Basic, Basic) +SUIT_ST(Basic, Basic_Setup, Basic_Tear_Down) { ADD_TEST(Faculty_Basic); ADD_TEST(Faculty_Negative); @@ -62,7 +58,29 @@ TEST(Other_Basic) ASSERT_FALSE(false); } -SUIT_ST(Other, Basic, EMPTY) { ADD_TEST(Other_Basic); } +TEST(Other_With_Malloc) +{ + int *some_array = set_malloc(20 * sizeof(int)); + + for (int i = 0; i < 20; i++) + { + some_array[i] = i; + } + + for (int i = 0; i < 20; i++) + { + fprintf(stdout, "%d ", some_array[i]); + } + fprintf(stdout, ".\n"); + + ASSERT_TRUE(false); +} + +SUIT_ST(Other, Basic_Setup, EMPTY) +{ + ADD_TEST(Other_Basic); + ADD_TEST(Other_With_Malloc); +} BUNDLE() {