refactor: (test) change test/assert usage

This commit is contained in:
thetek 2023-02-12 17:27:48 +01:00
parent a8641e4c33
commit 260d5559f3
3 changed files with 121 additions and 67 deletions

View File

@ -4,36 +4,39 @@
#include <stdbool.h>
#include <stddef.h>
#define test_add(suite, success_and_assertion, name) test_suite_add (suite, success_and_assertion, name, __FILE__, __LINE__)
#define test_assert_op(a, op, b) ((a) op (b)), (#a " " #op " " #b)
#define test_assert(a) ((bool) a), (#a)
#define test_add(group, success_and_assertion, name) test_group_add (group, success_and_assertion, name, __FILE__, __LINE__) /* add a test to a test group. source file and line will be inserted automatically. to be used with the `test_assert()` macro. */
#define test_assert(a) ((bool) (a)), (#a) /* provides the `success` and `assertion` parameters for `test_add()` / `test_group_add()` */
/* results of a test group or suite */
typedef struct
{
size_t success;
size_t failure;
size_t success; /* number of successful tests */
size_t failure; /* number of failed tests */
} test_results_t;
/* function signature for test group functions */
typedef test_results_t (*test_entry_func_t) (void);
/* test suite. a suite contains multiple test groups. */
typedef struct
{
test_entry_func_t *entries;
size_t entries_len;
size_t entries_cap;
} test_group_t;
test_entry_func_t *entries; /* test groups as executable function pointers */
size_t entries_len; /* number of functions */
size_t entries_cap; /* allocated space for functions */
} test_suite_t;
/* test group. contains multiple tests. */
typedef struct
{
test_results_t results;
} test_suite_t;
} test_group_t;
test_group_t test_group_new (void);
void test_group_free (test_group_t *group);
void test_group_add (test_group_t *group, test_entry_func_t func);
test_results_t test_group_run (test_group_t *group);
test_suite_t test_suite_new (void);
void test_suite_add (test_suite_t *suite, bool success, const char *assertion, const char *name, const char *file, size_t line);
test_results_t test_suite_get_results (test_suite_t *suite);
test_suite_t test_suite_new (void); /* create a new test suite */
void test_suite_free (test_suite_t *suite); /* free dynamically allocated memory occupied by a test suite */
void test_suite_add (test_suite_t *suite, test_entry_func_t func); /* add a test group function to a test suite */
test_results_t test_suite_run (test_suite_t *suite); /* run the entries within a test suite and print an informational message */
test_group_t test_group_new (void); /* create a new test group */
void test_group_add (test_group_t *group, bool success, const char *assertion, const char *name, const char *file, size_t line); /* add a test to a test group, print an error message if the test failed */
test_results_t test_group_get_results (test_group_t *group); /* get results of a test group. used for returning the status from a test group */
#endif // CUTILS_TEST_H_

View File

@ -5,89 +5,140 @@
#define TEST_GROUP_ENTRIES_ALLOC 16
test_group_t
test_group_new (void)
/**
* create a new test suite.
*
* @return test suite struct instance, with space allocated for adding test
* group functions.
*/
test_suite_t
test_suite_new (void)
{
test_group_t group;
test_suite_t suite;
group = (test_group_t) {0};
if ((group.entries = malloc ((sizeof *(group.entries)) * TEST_GROUP_ENTRIES_ALLOC)) == NULL)
suite = (test_suite_t) {0};
if ((suite.entries = malloc ((sizeof *(suite.entries)) * TEST_GROUP_ENTRIES_ALLOC)) == NULL)
{
log_error ("failed to allocate memory\n");
exit (EXIT_FAILURE);
}
group.entries[0] = NULL;
group.entries_cap = TEST_GROUP_ENTRIES_ALLOC;
suite.entries[0] = NULL;
suite.entries_cap = TEST_GROUP_ENTRIES_ALLOC;
return group;
return suite;
}
/**
* free dynamically allocated memory occupied by a test suite
*
* @param suite: a pointer to the suite to free up
*/
void
test_group_free (test_group_t *group)
test_suite_free (test_suite_t *suite)
{
free (group->entries);
free (suite->entries);
}
/**
* add a test group function to a test suite.
*
* @param suite: the suite to add the test group function to
* @param func: the test group function
*/
void
test_group_add (test_group_t *group, test_entry_func_t func)
test_suite_add (test_suite_t *suite, test_entry_func_t func)
{
if (group->entries_len == group->entries_cap - 1)
if (suite->entries_len == suite->entries_cap - 1)
{
group->entries_cap += TEST_GROUP_ENTRIES_ALLOC;
if ((group->entries = realloc (group->entries, (sizeof *(group->entries)) * group->entries_cap)) == NULL)
suite->entries_cap += TEST_GROUP_ENTRIES_ALLOC;
if ((suite->entries = realloc (suite->entries, (sizeof *(suite->entries)) * suite->entries_cap)) == NULL)
{
log_error ("failed to allocate memory\n");
exit (EXIT_FAILURE);
}
}
group->entries[group->entries_len] = func;
group->entries_len += 1;
group->entries[group->entries_len] = NULL;
suite->entries[suite->entries_len] = func;
suite->entries_len += 1;
suite->entries[suite->entries_len] = NULL;
}
/**
* run the entries within a test suite and print a message informing the user
* about the number of passed or failed tests.
*
* @param suite: the suite to run
*
* @return test results (number of successful and failed tests)
*/
test_results_t
test_group_run (test_group_t *group)
test_suite_run (test_suite_t *suite)
{
test_results_t total_result, result;
size_t i;
total_result = (test_results_t) {0};
for (i = 0; i < group->entries_len; i++)
for (i = 0; i < suite->entries_len; i++)
{
result = group->entries[i]();
result = suite->entries[i]();
total_result.success += result.success;
total_result.failure += result.failure;
}
if (total_result.failure == 0)
log_ok ("all tests successful (%zu tests in %zu sections)\n", total_result.success, group->entries_len);
log_ok ("all tests successful (%zu tests in %zu groups)\n", total_result.success, suite->entries_len);
else
log_error ("tests unsuccessful (%zu tests in %zu sections, out of which %zu successful and %zu failures)\n", total_result.success + total_result.failure, group->entries_len, total_result.success, total_result.failure);
log_error ("tests unsuccessful (%zu tests in %zu groups, out of which %zu successful and %zu failures)\n", total_result.success + total_result.failure, suite->entries_len, total_result.success, total_result.failure);
return total_result;
}
test_suite_t
test_suite_new (void)
/**
* create a new test group.
*
* @return a test group struct instance
*/
test_group_t
test_group_new (void)
{
return (test_suite_t) {0};
return (test_group_t) {0};
}
/**
* add a test to a test group, update its status and print an error message if
* the test failed.
*
* @param group: the test group to add the test to
* @param success: wheter the assertion was successful
* @param assertion: a string containing the assertion expression
* @param file: the file in which the test was added
* @param line: the source code line where the test was added
*
* @usage the header file `test.h` defines various macros for using this
* function. file name and line will be inserted automatically, and
* assertion macros may be used to fill in the `success` and
* `assertion` parameters.
*/
void
test_suite_add (test_suite_t *suite, bool success, const char *assertion, const char *name, const char *file, size_t line)
test_group_add (test_group_t *group, bool success, const char *assertion, const char *name, const char *file, size_t line)
{
if (success)
suite->results.success += 1;
group->results.success += 1;
else
{
suite->results.failure += 1;
group->results.failure += 1;
log_print_fl (LOG_LEVEL_ERROR, file, line, "test '%s' (assertion '%s') failed\n", name, assertion);
}
}
/**
* get results of a test group. used for returning the status from a test group
* function.
*
* @param group: the group to query
*/
test_results_t
test_suite_get_results (test_suite_t *suite)
test_group_get_results (test_group_t *group)
{
return suite->results;
return group->results;
}

View File

@ -12,48 +12,48 @@
test_results_t
test_cstr (void)
{
test_suite_t suite;
test_group_t group;
size_t len;
suite = test_suite_new ();
group = test_group_new ();
char s[] = " Hello, World!\n ";
len = strtrimr (s);
test_add (&suite, test_assert_op (len, ==, 17), "strtrimr return value");
test_add (&suite, test_assert_op (strlen (s), ==, 17), "strtrimr length");
test_add (&group, test_assert (len == 17), "strtrimr return value");
test_add (&group, test_assert (strlen (s) == 17), "strtrimr length");
char *sn = strtriml (s);
test_add (&suite, test_assert_op (sn, ==, s + 4), "strtriml pointer");
test_add (&suite, test_assert_op (strlen (sn), ==, 13), "strtriml length");
test_add (&group, test_assert (sn == s + 4), "strtriml pointer");
test_add (&group, test_assert (strlen (sn) == 13), "strtriml length");
char s2[] = " Hello, World!\n ";
sn = strtrim (s2);
test_add (&suite, test_assert_op (sn, ==, s2 + 4), "strtrim pointer");
test_add (&suite, test_assert_op (strlen (s2), ==, 17), "strtrim original pointer length");
test_add (&suite, test_assert_op (strlen (sn), ==, 13), "strtrim new pointer length");
test_add (&group, test_assert (sn == s2 + 4), "strtrim pointer");
test_add (&group, test_assert (strlen (s2) == 17), "strtrim original pointer length");
test_add (&group, test_assert (strlen (sn) == 13), "strtrim new pointer length");
char *s3 = "Hello, World!\n";
test_add (&suite, test_assert_op (strcount (s3, 'l'), ==, 3), "strcount multiple occurances");
test_add (&suite, test_assert_op (strcount (s3, '\n'), ==, 1), "strcount one occurance");
test_add (&suite, test_assert_op (strcount (s3, 'X'), ==, 0), "strcount no occurance");
test_add (&group, test_assert (strcount (s3, 'l') == 3), "strcount multiple occurances");
test_add (&group, test_assert (strcount (s3, '\n') == 1), "strcount one occurance");
test_add (&group, test_assert (strcount (s3, 'X') == 0), "strcount no occurance");
char s4[] = "Hello, World!\n";
char s5[] = "Hello, World!\n";
strdowncase (s4);
strupcase (s5);
test_add (&suite, test_assert (!strcmp (s4, "hello, world!\n")), "strdowncase");
test_add (&suite, test_assert (!strcmp (s5, "HELLO, WORLD!\n")), "strupcase");
test_add (&group, test_assert (!strcmp (s4, "hello, world!\n")), "strdowncase");
test_add (&group, test_assert (!strcmp (s5, "HELLO, WORLD!\n")), "strupcase");
return test_suite_get_results (&suite);
return test_group_get_results (&group);
}
int
main (void)
{
test_group_t group;
test_suite_t suite;
group = test_group_new ();
test_group_add (&group, test_cstr);
test_group_run (&group);
test_group_free (&group);
suite = test_suite_new ();
test_suite_add (&suite, test_cstr);
test_suite_run (&suite);
test_suite_free (&suite);
return EXIT_SUCCESS;
}