diff --git a/.drone.yml b/.drone.yml index 3907325..f98e385 100644 --- a/.drone.yml +++ b/.drone.yml @@ -35,10 +35,10 @@ steps: - name: Run tests image: registry.riba-interactive.de/alpine-build:1 commands: - - cmake -S. -Bbuild - - cd build + - cmake -S. -Bcmake-build-ci -DCMAKE_BUILD_TYPE=Debug -DENABLE_TEST_COVERAGE=on + - cd cmake-build-ci - cmake --build . - - ctest + - ctest -T Test -T Coverage trigger: branch: diff --git a/tests/bdd-for-c-ext.h b/tests/bdd-for-c-ext.h new file mode 100644 index 0000000..e1b4021 --- /dev/null +++ b/tests/bdd-for-c-ext.h @@ -0,0 +1,164 @@ +// +// Created by Rick Barenthin on 09.02.22. +// + +#ifndef WAITUI_BDD_FOR_C_EXT_H +#define WAITUI_BDD_FOR_C_EXT_H + +#include "bdd-for-c.h" + +#define __BDD_EXT_1ST_PARAM_FMT__ "'%s'" +#define __BDD_EXT_OTHER_PARAM_FMT__ ", '%s'" + +/** + * Create the format based on the count passed. + * @param num The number of format string parameters + * @return + */ +static inline char *__bdd_ext_build_format__(int num) { + size_t length = (sizeof(__BDD_EXT_1ST_PARAM_FMT__) - 1) + + (num - 1) * (sizeof(__BDD_EXT_OTHER_PARAM_FMT__) - 1) + 1; + char *fmt = calloc(length, sizeof(*fmt)); + if (!fmt) { + perror("calloc(__bdd_ext_build_format__)"); + abort(); + } + + for (int i = 0; i < num; i++) { + if (i != 0) { + strcat(fmt, __BDD_EXT_OTHER_PARAM_FMT__); + } else { + strcat(fmt, __BDD_EXT_1ST_PARAM_FMT__); + } + } + + return fmt; +} + +/** + * Create a format string from a test description and format parameters. + * + * @param description The description of the test case + * @param num The number of format string parameters + * @param ... The format string parameters a comma + * separated arguments + * @return The created format string + */ +static inline char *__bdd_ext_format_string_params__(const char *description, + int num, ...) { + va_list args; + va_list copy; + + int length = 1; + int written = 0; + char *fmt_string = NULL; + char *fmt = __bdd_ext_build_format__(num); + + va_start(args, num); + va_copy(copy, args); + length += snprintf(NULL, 0, "%s [", description); + length += vsnprintf(NULL, 0, fmt, copy); + length += snprintf(NULL, 0, "]"); + fmt_string = calloc(length, sizeof(*fmt_string)); + if (!fmt_string) { + perror("calloc(__bdd_ext_format_string_params__)"); + abort(); + } + va_end(copy); + written += snprintf(fmt_string, length, "%s [", description); + written += vsnprintf(fmt_string + written, length - written, fmt, args); + written += snprintf(fmt_string + written, length - written, "]"); + va_end(args); + + free(fmt); + + return fmt_string; +} + + +/** + * Create an array of `#test_param` structs. + * + * @note This macro must be used for passing test parameters to `for_it()`. + * @example: test_params({"input", "expected"}, {"input1", "expected1"}) + */ +#define test_params(...) \ + { __VA_ARGS__ } + +/** + * Define arguments for a format string as one macro argument. + * + * @note This macro must be used for passing test parameters to `for_it()`. + * @example: format_args(STR_FMT(&string), STR_FMT(&string1)); + */ +#define format_args(...) __VA_ARGS__ + +/** + * Define arguments for a format string as one macro argument. + * + * The first argument must be the number of format string parameters. + * + * @example: params_format(1, "%s", "%d"); + */ +#define params_format(...) __VA_ARGS__ + +/** + * Create a parametrized test case. + * + * Each test parameter results in an isolated test case which is + * executed independently and has its own output showing the test + * description followed by the given test parameters. + * + * @note: The given test parameters can be accessed by `#test_param`. + * @note: You must call `for_it_end()` after the test code to free + * allocated memory. + * + * @example: for_it("returns 1 and sets claim on existing claim", + * params_format(2, "%s", "%d"), + * format_args(test_param.input, test_param.expected), + * { char *input; int expected; }, + * test_params( + * {"test input 1", 0}, + * {"test input 2", 1}, + * {"test input 4", 2}, + * {"test input 5", 3} + * ) + * ) + * + * @param description: The description of the test + * @param params_format: A format string for printing the parameters + * of a test. Use `params_format()` macro for + * format creation. + * @param format_args: The arguments for the parameter format. Use + * `format_args()` macro for arguments creation. + * @param param_struct_body: The struct to be created holding the test + * parameters. + * @param params: The test parameters to be used + */ +#define for_it(description, params_format, format_args, param_struct_body, \ + params) \ + do { \ + struct __bdd_ext_test_param__ param_struct_body \ + __bdd_ext_test_params__[] = params; \ + int __bdd_ext_input_size__ = sizeof(__bdd_ext_test_params__) / \ + sizeof(__bdd_ext_test_params__[0]); \ + for (int __bdd_ext_i__ = 0; __bdd_ext_i__ < __bdd_ext_input_size__; \ + __bdd_ext_i__++) { \ + struct __bdd_ext_test_param__ test_param = \ + __bdd_ext_test_params__[__bdd_ext_i__]; \ + char *__bdd_ext_param_format_string__ = \ + __bdd_ext_format_string_params__(description, \ + params_format); \ + it(__bdd_ext_param_format_string__, format_args) { + +/** + * End a parametrized test and free all resources allocated in #for_it. + */ +#define for_it_end() \ + } \ + free(__bdd_ext_param_format_string__); \ + } \ + } \ + while (0) + +#endif//WAITUI_BDD_FOR_C_EXT_H diff --git a/tests/bdd-for-c.h b/tests/bdd-for-c.h index 7c6ec57..cf3b70d 100644 --- a/tests/bdd-for-c.h +++ b/tests/bdd-for-c.h @@ -664,7 +664,7 @@ for(\ #define __BDD_CHECK_ONE__(condition) __BDD_CHECK__(condition, #condition) -#define check(...) __BDD_MACRO__(__BDD_CHECK_, __VA_ARGS__) +#define check(...) do { __BDD_MACRO__(__BDD_CHECK_, __VA_ARGS__) } while (0) #ifdef _MSC_VER #pragma warning(pop) diff --git a/tests/library/list/test_list.c b/tests/library/list/test_list.c index f42366a..0795ac5 100644 --- a/tests/library/list/test_list.c +++ b/tests/library/list/test_list.c @@ -7,7 +7,7 @@ #include "waitui/list.h" -#include "../../bdd-for-c.h" +#include "../../bdd-for-c-ext.h" typedef struct value { int count; @@ -31,54 +31,49 @@ CREATE_LIST_TYPE(INTERFACE, value) CREATE_LIST_TYPE(IMPLEMENTATION, value) spec("list") { - context("use as a stack") { - static value_list *stack = NULL; + context("use as a normal list") { + static value_list *list = NULL; - before() { stack = value_list_new(); } - - after() { value_list_destroy(&stack); } - - it("should have a stack") { - check(stack != NULL, "stack should not be NULL"); + before_each() { + list = value_list_new(); + check(list != NULL, "list should not be NULL"); } - it("should allow to push a value onto the stack") { - value *value1 = value_new(1); - check(value1 != NULL, "value1 should not be NULL"); - check(value_list_push(stack, value1) == true, - "value1 should be added to the stack"); + after_each() { + value_list_destroy(&list); + check(list == NULL, "list should be NULL"); } - it("should allow to push a second value onto the stack") { - value *value2 = value_new(2); - check(value2 != NULL, "value2 should not be NULL"); - check(value_list_push(stack, value2) == true, - "value1 should be added to the stack"); - } + describe("value_list_push()") { + for_it( + "should push values onto the end list and it should be there", + params_format(2, "start: %d", "elements: %d"), + format_args(test_param.start, test_param.count), + { + int start; + int count; + }, + test_params( + {0, 0}, + {0, 1}, + {1, 1}, + {4, 20}, + ) + ) { + for (int i = test_param.start; i < test_param.start + test_param.count; ++i) { + check(value_list_push(list, value_new(i)) == 1); + value *value = value_list_peek(list); + check(value->i == i, "value should be %d but is %d", i, value->i); + } - it("should return the last added element on pop") { - value *test = value_list_pop(stack); - check(test != NULL, "value_list_pop should not return NULL"); - check(test->i == 2, "test should be 2"); - value_destroy(&test); - } - - it("should be able to peek the top element") { - value *test = value_list_peek(stack); - check(test != NULL, "value_list_peek should not return NULL"); - check(test->i == 1, "test should be 1"); - } - - it("should return the first added element on pop") { - value *test = value_list_pop(stack); - check(test != NULL, "value_list_pop should not return NULL"); - check(test->i == 1, "test should be 1"); - value_destroy(&test); - } - - it("should return NULL if peek with an empty stack") { - value *test = value_list_peek(stack); - check(test == NULL, "value_list_peek should return NULL"); + for (int i = test_param.start + test_param.count - 1; i >= test_param.start; --i) { + value *value = value_list_pop(list); + check(value != NULL, "value should not be NULL"); + check(value->i == i, "value should be %d but is %d", i, value->i); + value_destroy(&value); + } + } + for_it_end(); } } } \ No newline at end of file