diff options
-rw-r--r-- | CMakeLists.txt | 10 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 28 | ||||
-rw-r--r-- | tests/test_docker.c | 71 | ||||
-rw-r--r-- | tests/test_relocation.c | 62 | ||||
-rw-r--r-- | tests/test_str.c | 379 | ||||
-rw-r--r-- | tests/test_strlist.c | 613 | ||||
-rw-r--r-- | tests/test_system.c | 145 | ||||
-rw-r--r-- | tests/test_template.c | 100 | ||||
-rw-r--r-- | tests/test_utils.c | 477 | ||||
-rw-r--r-- | tests/testing.h | 170 |
10 files changed, 2055 insertions, 0 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 0824baa..66fa91b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,16 @@ endif() add_subdirectory(src) +option(BUILD_TESTING_DEBUG OFF) +if (BUILD_TESTING_DEBUG) + add_compile_options(DEBUG) +endif() +option(BUILD_TESTING OFF) +if (BUILD_TESTING) + enable_testing() + add_subdirectory(tests) +endif() + set(SYSCONFDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SYSCONFDIR}") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/include/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/config.h @ONLY) install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/omc.ini DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/omc) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..dd349e2 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,28 @@ +include_directories( + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include +) +set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/tests) +set(CTEST_BINARY_DIRECTORY ${PROJECT_BINARY_DIR}/tests) + +file(GLOB files "test_*.c") + +foreach(file ${files}) + string(REGEX REPLACE "(^.*/|\\.[^.]*$)" "" file_without_ext ${file}) + add_executable(${file_without_ext} ${file}) + if(NOT(MSVC)) + target_compile_options(${file_without_ext} PRIVATE + -Wno-unused-parameter + -Wno-unused-but-set-variable + -Wno-incompatible-pointer-types-discards-qualifiers + -Wno-discarded-qualifiers) + endif() + target_link_libraries(${file_without_ext} PRIVATE ohmycal) + add_test(${file_without_ext} ${file_without_ext}) + set_tests_properties(${file_without_ext} + PROPERTIES + TIMEOUT 120) + set_tests_properties(${file_without_ext} + PROPERTIES + SKIP_RETURN_CODE 127) +endforeach()
\ No newline at end of file diff --git a/tests/test_docker.c b/tests/test_docker.c new file mode 100644 index 0000000..223de2a --- /dev/null +++ b/tests/test_docker.c @@ -0,0 +1,71 @@ +#include "testing.h" +struct DockerCapabilities cap_suite; + +void test_docker_capable() { + struct DockerCapabilities cap; + int result = docker_capable(&cap); + OMC_ASSERT(result == true, "docker installation unusable"); + OMC_ASSERT(cap.available, "docker is not available"); + OMC_ASSERT(cap.build != 0, "docker cannot build images"); + // no cap.podman assertion. doesn't matter if we have docker or podman + OMC_ASSERT(cap.usable, "docker is unusable"); +} + +void test_docker_exec() { + OMC_ASSERT(docker_exec("system info", 0) == 0, "unable to print docker information"); + OMC_ASSERT(docker_exec("system info", OMC_DOCKER_QUIET) == 0, "unable to be quiet"); +} + +void test_docker_sanitize_tag() { + const char *data = " !\"#$%&'()*+,-;<=>?@[\\]^_`{|}~"; + char *input = strdup(data); + docker_sanitize_tag(input); + int result = 0; + for (size_t i = 0; i < strlen(data); i++) { + if (input[i] != '-') { + result = 1; + break; + } + } + OMC_ASSERT(result == 0, "proper tag characters were not replaced correctly"); + guard_free(input); +} + +void test_docker_build_and_script_and_save() { + const char *dockerfile_contents = "FROM alpine:latest\nCMD [\"sh\", \"-l\"]\n"; + mkdir("test_docker_build", 0755); + if (!pushd("test_docker_build")) { + omc_testing_write_ascii("Dockerfile", dockerfile_contents); + OMC_ASSERT(docker_build(".", "--arch $(uname -m) -t test_docker_build", cap_suite.build) == 0, "docker build test failed"); + OMC_ASSERT(docker_script("test_docker_build", "uname -a", 0) == 0, "simple docker container script execution failed"); + OMC_ASSERT(docker_save("test_docker_build", ".", OMC_DOCKER_IMAGE_COMPRESSION) == 0, "saving a simple image failed"); + OMC_ASSERT(docker_exec("load < test_docker_build.tar.*", 0) == 0, "loading a simple image failed"); + docker_exec("image rm -f test_docker_build", 0); + remove("Dockerfile"); + popd(); + remove("test_docker_build"); + } else { + OMC_ASSERT(false, "dir change failed"); + return; + } +} + +void test_docker_validate_compression_program() { + OMC_ASSERT(docker_validate_compression_program(OMC_DOCKER_IMAGE_COMPRESSION) == 0, "baked-in compression program does not exist"); +} + +int main(int argc, char *argv[]) { + OMC_TEST_BEGIN_MAIN(); + if (!docker_capable(&cap_suite)) { + return OMC_TEST_SUITE_SKIP; + } + OMC_TEST_FUNC *tests[] = { + test_docker_capable, + test_docker_exec, + test_docker_build_and_script_and_save, + test_docker_sanitize_tag, + test_docker_validate_compression_program, + }; + OMC_TEST_RUN(tests); + OMC_TEST_END_MAIN(); +}
\ No newline at end of file diff --git a/tests/test_relocation.c b/tests/test_relocation.c new file mode 100644 index 0000000..0796074 --- /dev/null +++ b/tests/test_relocation.c @@ -0,0 +1,62 @@ +#include "testing.h" + +static const char *test_string = "The quick brown fox jumps over the lazy dog."; +static const char *targets[] = { + "The", "^^^ quick brown fox jumps over the lazy dog.", + "quick", "The ^^^ brown fox jumps over the lazy dog.", + "brown fox jumps over the", "The quick ^^^ lazy dog.", + "lazy", "The quick brown fox jumps over the ^^^ dog.", + "dog", "The quick brown fox jumps over the lazy ^^^.", +}; + +void test_replace_text() { + for (size_t i = 0; i < sizeof(targets) / sizeof(*targets); i += 2) { + const char *target = targets[i]; + const char *expected = targets[i + 1]; + char input[BUFSIZ] = {0}; + strcpy(input, test_string); + + OMC_ASSERT(replace_text(input, target, "^^^", 0) == 0, "string replacement failed"); + OMC_ASSERT(strcmp(input, expected) == 0, "unexpected replacement"); + } + +} + +void test_file_replace_text() { + for (size_t i = 0; i < sizeof(targets) / sizeof(*targets); i += 2) { + const char *filename = "test_file_replace_text.txt"; + const char *target = targets[i]; + const char *expected = targets[i + 1]; + FILE *fp; + + fp = fopen(filename, "w"); + if (fp) { + fprintf(fp, "%s", test_string); + fclose(fp); + OMC_ASSERT(file_replace_text(filename, target, "^^^", 0) == 0, "string replacement failed"); + } else { + OMC_ASSERT(false, "failed to open file for writing"); + return; + } + + char input[BUFSIZ] = {0}; + fp = fopen(filename, "r"); + if (fp) { + fread(input, sizeof(*input), sizeof(input), fp); + OMC_ASSERT(strcmp(input, expected) == 0, "unexpected replacement"); + } else { + OMC_ASSERT(false, "failed to open file for reading"); + return; + } + } +} + +int main(int argc, char *argv[]) { + OMC_TEST_BEGIN_MAIN(); + OMC_TEST_FUNC *tests[] = { + test_replace_text, + test_file_replace_text, + }; + OMC_TEST_RUN(tests); + OMC_TEST_END_MAIN(); +}
\ No newline at end of file diff --git a/tests/test_str.c b/tests/test_str.c new file mode 100644 index 0000000..2321ccd --- /dev/null +++ b/tests/test_str.c @@ -0,0 +1,379 @@ +#include "testing.h" + +void test_to_short_version() { + struct testcase { + const char *data; + const char *expected; + }; + + struct testcase tc[] = { + {.data = "1.2.3", .expected = "123"}, + {.data = "py3.12", .expected = "py312"}, + {.data = "generic-1.2.3", .expected = "generic-123"}, + {.data = "nothing to do", .expected = "nothing to do"}, + }; + + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char *result = to_short_version(tc[i].data); + OMC_ASSERT_FATAL(result != NULL, "should not be NULL"); + OMC_ASSERT(strcmp(result, tc[i].expected) == 0, "unexpected result"); + guard_free(result); + } + +} + +void test_tolower_s() { + struct testcase { + const char *data; + const char *expected; + }; + + struct testcase tc[] = { + {.data = "HELLO WORLD", .expected = "hello world"}, + {.data = "Hello World", .expected = "hello world"}, + {.data = "hello world", .expected = "hello world"}, + }; + + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char input[100] = {0}; + strcpy(input, tc[i].data); + tolower_s(input); + OMC_ASSERT(strcmp(input, tc[i].expected) == 0, "unexpected result"); + } +} + +void test_isdigit_s() { + struct testcase { + const char *data; + int expected; + }; + + struct testcase tc[] = { + {.data = "", .expected = false}, + {.data = "1", .expected = true}, + {.data = "1234567890", .expected = true}, + {.data = " 1 ", .expected = false}, + {.data = "a number", .expected = false}, + {.data = "10 numbers", .expected = false}, + {.data = "10 10", .expected = false} + }; + + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + OMC_ASSERT(isdigit_s(tc[i].data) == tc[i].expected, "unexpected result"); + } +} + +void test_strdup_array_and_strcmp_array() { + struct testcase { + const char **data; + const char **expected; + }; + const struct testcase tc[] = { + {.data = (const char *[]) {"abc", "123", NULL}, + .expected = (const char *[]) {"abc", "123", NULL}}, + {.data = (const char *[]) {"I", "have", "a", "pencil", "box", NULL}, + .expected = (const char *[]) {"I", "have", "a", "pencil", "box", NULL}}, + }; + + OMC_ASSERT(strdup_array(NULL) == NULL, "returned non-NULL on NULL argument"); + for (size_t outer = 0; outer < sizeof(tc) / sizeof(*tc); outer++) { + char **result = strdup_array((char **) tc[outer].data); + OMC_ASSERT(strcmp_array((const char **) result, tc[outer].expected) == 0, "array members were different"); + } + + const struct testcase tc_bad[] = { + {.data = (const char *[]) {"ab", "123", NULL}, + .expected = (const char *[]) {"abc", "123", NULL}}, + {.data = (const char *[]) {"have", "a", "pencil", "box", NULL}, + .expected = (const char *[]) {"I", "have", "a", "pencil", "box", NULL}}, + }; + + OMC_ASSERT(strcmp_array(tc[0].data, NULL) > 0, "right argument is NULL, expected positive return"); + OMC_ASSERT(strcmp_array(NULL, tc[0].data) < 0, "left argument is NULL, expected negative return"); + OMC_ASSERT(strcmp_array(NULL, NULL) == 0, "left and right arguments are NULL, expected zero return"); + for (size_t outer = 0; outer < sizeof(tc_bad) / sizeof(*tc_bad); outer++) { + char **result = strdup_array((char **) tc_bad[outer].data); + OMC_ASSERT(strcmp_array((const char **) result, tc_bad[outer].expected) != 0, "array members were identical"); + } +} + +void test_strchrdel() { + struct testcase { + const char *data; + const char *input; + const char *expected; + }; + const struct testcase tc[] = { + {.data ="aaaabbbbcccc", .input = "ac", .expected = "bbbb"}, + {.data = "1I 2have 3a 4pencil 5box.", .input = "1245", .expected = "I have 3a pencil box."}, + {.data = "\v\v\vI\t\f ha\tve a\t pen\tcil b\tox.", .input = " \f\t\v", "Ihaveapencilbox."}, + }; + + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char *data = strdup(tc[i].data); + strchrdel(data, tc[i].input); + OMC_ASSERT(strcmp(data, tc[i].expected) == 0, "wrong status for condition"); + guard_free(data); + } +} + +void test_endswith() { + struct testcase { + const char *data; + const char *input; + const int expected; + }; + struct testcase tc[] = { + {.data = "I have a pencil box.", .input = ".", .expected = true}, + {.data = "I have a pencil box.", .input = "box.", .expected = true}, + {.data = "I have a pencil box.", .input = "pencil box.", .expected = true}, + {.data = "I have a pencil box.", .input = "a pencil box.", .expected = true}, + {.data = "I have a pencil box.", .input = "have a pencil box.", .expected = true}, + {.data = "I have a pencil box.", .input = "I have a pencil box.", .expected = true}, + {.data = ". ", .input = ".", .expected = false}, + {.data = "I have a pencil box.", .input = "pencil box", .expected = false}, + {.data = NULL, .input = "test", .expected = -1}, + {.data = "I have a pencil box", .input = NULL, .expected = -1}, + {.data = NULL, .input = NULL, .expected = -1}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + int result = endswith(tc[i].data, tc[i].input); + OMC_ASSERT(result == tc[i].expected, "wrong status for condition"); + } +} + +void test_startswith() { + struct testcase { + const char *data; + const char *input; + const int expected; + }; + struct testcase tc[] = { + {.data = "I have a pencil box.", .input = "I", .expected = true}, + {.data = "I have a pencil box.", .input = "I have", .expected = true}, + {.data = "I have a pencil box.", .input = "I have a", .expected = true}, + {.data = "I have a pencil box.", .input = "I have a pencil", .expected = true}, + {.data = "I have a pencil box.", .input = "I have a pencil box", .expected = true}, + {.data = "I have a pencil box.", .input = "I have a pencil box.", .expected = true}, + {.data = " I have a pencil box.", .input = "I have", .expected = false}, + {.data = "I have a pencil box.", .input = "pencil box", .expected = false}, + {.data = NULL, .input = "test", .expected = -1}, + {.data = "I have a pencil box", .input = NULL, .expected = -1}, + {.data = NULL, .input = NULL, .expected = -1}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + int result = startswith(tc[i].data, tc[i].input); + OMC_ASSERT(result == tc[i].expected, "wrong status for condition"); + } +} + +void test_num_chars() { + struct testcase { + const char *data; + const char input; + const int expected; + }; + struct testcase tc[] = { + {.data = " ", .input = ' ', .expected = 9}, + {.data = "1 1 1 1 1", .input = '1', .expected = 5}, + {.data = "abc\t\ndef\nabc\ndef\n", .input = 'c', .expected = 2}, + {.data = "abc\t\ndef\nabc\ndef\n", .input = '\t', .expected = 1}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + OMC_ASSERT(num_chars(tc[i].data, tc[i].input) == tc[i].expected, "incorrect number of characters detected"); + } +} + +void test_split() { + struct testcase { + const char *data; + const size_t max_split; + const char *delim; + const char **expected; + }; + struct testcase tc[] = { + {.data = "a/b/c/d/e", .delim = "/", .max_split = 0, .expected = (const char *[]) {"a", "b", "c", "d", "e", NULL}}, + {.data = "a/b/c/d/e", .delim = "/", .max_split = 1, .expected = (const char *[]) {"a", "b/c/d/e", NULL}}, + {.data = "a/b/c/d/e", .delim = "/", .max_split = 2, .expected = (const char *[]) {"a", "b", "c/d/e", NULL}}, + {.data = "a/b/c/d/e", .delim = "/", .max_split = 3, .expected = (const char *[]) {"a", "b", "c", "d/e", NULL}}, + {.data = "a/b/c/d/e", .delim = "/", .max_split = 4, .expected = (const char *[]) {"a", "b", "c", "d", "e", NULL}}, + {.data = "multiple words split n times", .delim = " ", .max_split = 0, .expected = (const char *[]) {"multiple", "words", "split", "n", "times", NULL}}, + {.data = "multiple words split n times", .delim = " ", .max_split = 1, .expected = (const char *[]) {"multiple", "words split n times", NULL}}, + {.data = NULL, .delim = NULL, NULL}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char **result; + result = split(tc[i].data, tc[i].delim, tc[i].max_split); + OMC_ASSERT(strcmp_array(result, tc[i].expected) == 0, "Split failed"); + GENERIC_ARRAY_FREE(result); + } +} + +void test_join() { + struct testcase { + const char **data; + const char *delim; + const char *expected; + }; + struct testcase tc[] = { + {.data = (const char *[]) {"a", "b", "c", "d", "e", NULL}, .delim = "", .expected = "abcde"}, + {.data = (const char *[]) {"a", NULL}, .delim = "", .expected = "a"}, + {.data = (const char *[]) {"a", "word", "or", "two", NULL}, .delim = "/", .expected = "a/word/or/two"}, + {.data = NULL, .delim = NULL, .expected = ""}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char *result; + result = join(tc[i].data, tc[i].delim); + OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array"); + guard_free(result); + } +} + +void test_join_ex() { + struct testcase { + const char *delim; + const char *expected; + }; + struct testcase tc[] = { + {.delim = "", .expected = "abcde"}, + {.delim = "/", .expected = "a/b/c/d/e"}, + {.delim = "\n", .expected = "a\nb\nc\nd\ne"}, + {.delim = "\n\n", .expected = "a\n\nb\n\nc\n\nd\n\ne"}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char *result; + result = join_ex(tc[i].delim, "a", "b", "c", "d", "e", NULL); + OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "failed to join array"); + guard_free(result); + } +} + +void test_substring_between() { + struct testcase { + const char *data; + const char *delim; + const char *expected; + }; + struct testcase tc[] = { + {.data = "I like {{adjective}} spaceships", .delim = "{{}}", .expected = "adjective"}, + {.data = "I like {adjective} spaceships", .delim = "{}", .expected = "adjective"}, + {.data = "one = 'two'", .delim = "''", .expected = "two"}, + {.data = "I like {adjective> spaceships", .delim = "{}", .expected = ""}, // no match + {.data = NULL, .delim = NULL, .expected = ""}, // both arguments NULL + {.data = "nothing!", .delim = NULL, .expected = ""}, // null delim + {.data = "", .delim = "{}", .expected = ""}, // no match + {.data = NULL, .delim = "nothing!", .expected = ""}, // null sptr + {.data = "()", .delim = "(", .expected = ""}, // delim not divisible by 2 + {.data = "()", .delim = "( )", .expected = ""}, // delim not divisible by 2 + {.data = "nothing () here", .delim = "()", .expected = ""}, // nothing exists between delimiters + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char *result = substring_between(tc[i].data, tc[i].delim); + printf("data = '%s', delim = '%s', expected = '%s', result = '%s'\n", tc[i].data, tc[i].delim, tc[i].expected, result); + OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "unable to extract substring"); + guard_free(result); + } +} + +void test_strdeldup() { + struct testcase { + const char **data; + const char **expected; + }; + struct testcase tc[] = { + {.data = NULL, .expected = NULL}, + {.data = (const char *[]) {"a", "a", "a", "b", "b", "b", "c", "c", "c", NULL}, .expected = (const char *[]) {"a", "b", "c", NULL}}, + {.data = (const char *[]) {"a", "b", "c", "a", "b", "c", "a", "b", "c", NULL}, .expected = (const char *[]) {"a", "b", "c", NULL}}, + {.data = (const char *[]) {"apple", "banana", "coconut", NULL}, .expected = (const char *[]) {"apple", "banana", "coconut", NULL}}, + {.data = (const char *[]) {"apple", "banana", "apple", "coconut", NULL}, .expected = (const char *[]) {"apple", "banana", "coconut", NULL}}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char **result = strdeldup(tc[i].data); + OMC_ASSERT(strcmp_array(result, tc[i].expected) == 0, "incorrect number of duplicates removed"); + GENERIC_ARRAY_FREE(result); + } +} + +void test_strsort() { + +} + +void test_strstr_array() { + +} + +void test_lstrip() { + struct testcase { + const char *data; + const char *expected; + }; + struct testcase tc[] = { + {.data = "I am a string.\n", .expected = "I am a string.\n"}, // left strip only + {.data = "I am a string.\n", .expected = "I am a string.\n"}, + {.data = "\t\t\t\tI am a string.\n", .expected = "I am a string.\n"}, + {.data = " I am a string.\n", .expected = "I am a string.\n"}, + }; + + OMC_ASSERT(lstrip(NULL) == NULL, "incorrect return type"); + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char *buf = calloc(255, sizeof(*buf)); + char *result; + strcpy(buf, tc[i].data); + result = lstrip(buf); + OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-left"); + guard_free(buf); + } +} + +void test_strip() { + struct testcase { + const char *data; + const char *expected; + }; + struct testcase tc[] = { + {.data = " I am a string.\n", .expected = " I am a string."}, // right strip only + {.data = "\tI am a string.\n\t", .expected = "\tI am a string."}, + {.data = "\t\t\t\tI am a string.\n", .expected = "\t\t\t\tI am a string."}, + {.data = "I am a string.\n\n\n\n", .expected = "I am a string."}, + {.data = "", .expected = ""}, + {.data = " ", .expected = ""}, + }; + + OMC_ASSERT(strip(NULL) == NULL, "incorrect return type"); + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + char *buf = calloc(255, sizeof(*buf)); + char *result; + strcpy(buf, tc[i].data); + result = strip(buf); + printf("result = '%s', expected = '%s'\n", result, tc[i].expected); + OMC_ASSERT(strcmp(result ? result : "", tc[i].expected) == 0, "incorrect strip-from-right"); + guard_free(buf); + } +} + +void test_isempty() { + +} + +int main(int argc, char *argv[]) { + OMC_TEST_BEGIN_MAIN(); + OMC_TEST_FUNC *tests[] = { + test_to_short_version, + test_tolower_s, + test_isdigit_s, + test_strdup_array_and_strcmp_array, + test_strchrdel, + test_strdeldup, + test_lstrip, + test_strip, + test_num_chars, + test_startswith, + test_endswith, + test_split, + test_join, + test_join_ex, + test_substring_between, + }; + OMC_TEST_RUN(tests); + OMC_TEST_END_MAIN(); +}
\ No newline at end of file diff --git a/tests/test_strlist.c b/tests/test_strlist.c new file mode 100644 index 0000000..6111013 --- /dev/null +++ b/tests/test_strlist.c @@ -0,0 +1,613 @@ +#include "testing.h" + +#define BOILERPLATE_TEST_STRLIST_AS_TYPE(FUNC, TYPE) \ +do { \ + struct StrList *list; \ + list = strlist_init(); \ + puts("----"); \ + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { \ + strlist_append(&list, (char *) tc[i].data); \ + result = FUNC(list, i); \ + if (!strlist_errno) { \ + OMC_ASSERT(result == (TYPE) tc[i].expected, "unexpected numeric result"); \ + } \ + } \ + guard_strlist_free(&list); \ +} while (0) + +void test_strlist_init() { + struct StrList *list; + list = strlist_init(); + OMC_ASSERT(list != NULL, "list should not be NULL"); + OMC_ASSERT(list->num_alloc == 1, "fresh list should have one record allocated"); + OMC_ASSERT(list->num_inuse == 0, "fresh list should have no records in use"); + guard_strlist_free(&list); +} + +void test_strlist_free() { + struct StrList *list; + list = strlist_init(); + strlist_free(&list); + // Not entirely sure what to assert here. On failure, it'll probably segfault. +} + +void test_strlist_append() { + struct testcase { + const char *data; + const size_t expected_in_use; + const char *expected; + }; + + struct testcase tc[] = { + {.data = "I have a pencil box.", .expected_in_use = 1}, + {.data = "A rectangular pencil box.", .expected_in_use = 2}, + {.data = "If I need a pencil I know where to get one.", .expected_in_use = 3}, + }; + + struct StrList *list; + list = strlist_init(); + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + strlist_append(&list, (char *) tc[i].data); + OMC_ASSERT(list->num_inuse == tc[i].expected_in_use, "incorrect number of records in use"); + OMC_ASSERT(list->num_alloc == tc[i].expected_in_use + 1, "incorrect number of records allocated"); + OMC_ASSERT(strcmp(strlist_item(list, i), tc[i].data) == 0, "value was appended incorrectly. data mismatch."); + } + guard_strlist_free(&list); +} + +void test_strlist_append_many_records() { + struct StrList *list; + list = strlist_init(); + size_t maxrec = 10240; + const char *data = "There are many strings but this one is mine."; + for (size_t i = 0; i < maxrec; i++) { + strlist_append(&list, (char *) data); + } + OMC_ASSERT(strlist_count(list) == maxrec, "record count mismatch"); + guard_strlist_free(&list); +} + +void test_strlist_set() { + struct StrList *list; + list = strlist_init(); + size_t maxrec = 100; + const char *data = "There are many strings but this one is mine."; + const char *replacement_short = "A shorter string."; + const char *replacement_long = "A longer string ensures the array grows correctly."; + int set_resize_long_all_ok = 0; + int set_resize_short_all_ok = 0; + + for (size_t i = 0; i < maxrec; i++) { + strlist_append(&list, (char *) data); + } + + for (size_t i = 0; i < maxrec; i++) { + int result; + strlist_set(&list, i, (char *) replacement_long); + result = strcmp(strlist_item(list, i), replacement_long) == 0; + set_resize_long_all_ok += result; + } + OMC_ASSERT(set_resize_long_all_ok == (int) maxrec, "Replacing record with longer strings failed"); + + for (size_t i = 0; i < maxrec; i++) { + int result; + strlist_set(&list, i, (char *) replacement_short); + result = strcmp(strlist_item(list, i), replacement_short) == 0; + set_resize_short_all_ok += result; + } + OMC_ASSERT(set_resize_short_all_ok == (int) maxrec, "Replacing record with shorter strings failed"); + + OMC_ASSERT(strlist_count(list) == maxrec, "record count mismatch"); + guard_strlist_free(&list); +} + +void test_strlist_append_file() { + struct testcase { + const char *origin; + const char **expected; + }; + const char *expected[] = { + "Do not delete this file.\n", + "Do not delete this file.\n", + "Do not delete this file.\n", + "Do not delete this file.\n", + }; + const char *local_filename = "test_strlist_append_file.txt"; + + struct testcase tc[] = { + {.origin = "https://ssb.stsci.edu/jhunk/omc_test/test_strlist_append_file_from_remote.txt", .expected = expected}, + {.origin = local_filename, .expected = expected}, + }; + + FILE *fp; + fp = fopen(local_filename, "w"); + if (!fp) { + OMC_ASSERT(false, "unable to open local file for writing"); + } + for (size_t i = 0; i < sizeof(expected) / sizeof(*expected); i++) { + fprintf(fp, "%s", expected[i]); + } + fclose(fp); + + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + struct StrList *list; + list = strlist_init(); + strlist_append_file(list, (char *) tc[i].origin, NULL); + for (size_t z = 0; z < strlist_count(list); z++) { + const char *left; + const char *right; + left = strlist_item(list, z); + right = expected[z]; + OMC_ASSERT(strcmp(left, right) == 0, "file content is different than expected"); + } + OMC_ASSERT(strcmp_array(list->data, expected) == 0, "file contents does not match expected values"); + guard_strlist_free(&list); + } +} + +void test_strlist_append_strlist() { + struct StrList *left; + struct StrList *right; + left = strlist_init(); + right = strlist_init(); + + strlist_append(&right, "A"); + strlist_append(&right, "B"); + strlist_append(&right, "C"); + strlist_append_strlist(left, right); + OMC_ASSERT(strlist_cmp(left, right) == 0, "left and right should be identical"); + strlist_append_strlist(left, right); + OMC_ASSERT(strlist_cmp(left, right) == 1, "left and right should be different"); + OMC_ASSERT(strlist_count(left) > strlist_count(right), "left should be larger than right"); + guard_strlist_free(&left); + guard_strlist_free(&right); +} + +void test_strlist_append_array() { + const char *data[] = { + "Appending", + "Some", + "Data", + NULL, + }; + struct StrList *list; + list = strlist_init(); + strlist_append_array(list, (char **) data); + size_t result = 0; + for (size_t i = 0; i < strlist_count(list); i++) { + char *item = strlist_item(list, i); + result += strcmp(item, data[i]) == 0; + } + OMC_ASSERT(result == strlist_count(list), "result is not identical to input"); + guard_strlist_free(&list); +} + +void test_strlist_append_tokenize() { + const char *data = "This is a test.\nWe will split this string\ninto three list records\n"; + size_t line_count = num_chars(data, '\n'); + struct StrList *list; + list = strlist_init(); + strlist_append_tokenize(list, (char *) data, "\n"); + // note: the trailing '\n' in the data is parsed as a token + OMC_ASSERT(line_count + 1 == strlist_count(list), "unexpected number of lines in array"); + int trailing_item_is_empty = isempty(strlist_item(list, strlist_count(list) - 1)); + OMC_ASSERT(trailing_item_is_empty, "trailing record should be an empty string"); + guard_strlist_free(&list); +} + +void test_strlist_copy() { + struct StrList *list = strlist_init(); + struct StrList *list_copy; + + strlist_append(&list, "Make a copy"); + strlist_append(&list, "of this data"); + strlist_append(&list, "1:1, please"); + + OMC_ASSERT(strlist_copy(NULL) == NULL, "NULL argument should return NULL"); + + list_copy = strlist_copy(list); + OMC_ASSERT(strlist_cmp(list, list_copy) == 0, "list was copied incorrectly"); + guard_strlist_free(&list); + guard_strlist_free(&list_copy); +} + +void test_strlist_remove() { + const char *data[] = { + "keep this", + "remove this", + "keep this", + "remove this", + "keep this", + "remove this", + }; + struct StrList *list; + list = strlist_init(); + size_t data_size = sizeof(data) / sizeof(*data); + for (size_t i = 0; i < data_size; i++) { + strlist_append(&list, (char *) data[i]); + } + + size_t len_before = strlist_count(list); + size_t removed = 0; + for (size_t i = 0; i < len_before; i++) { + char *item = strlist_item(list, i); + if (startswith(item, "remove")) { + strlist_remove(list, i); + removed++; + } + } + size_t len_after = strlist_count(list); + OMC_ASSERT(len_before == data_size, "list length before modification is incorrect. new records?"); + OMC_ASSERT(len_after == (len_before - removed), "list length after modification is incorrect. not enough records removed."); + + guard_strlist_free(&list); +} + +void test_strlist_cmp() { + struct StrList *left; + struct StrList *right; + + left = strlist_init(); + right = strlist_init(); + OMC_ASSERT(strlist_cmp(NULL, NULL) == -1, "NULL first argument should return -1"); + OMC_ASSERT(strlist_cmp(left, NULL) == -2, "NULL second argument should return -2"); + OMC_ASSERT(strlist_cmp(left, right) == 0, "should be the same"); + strlist_append(&left, "left"); + OMC_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); + strlist_append(&right, "right"); + OMC_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); + strlist_append(&left, "left again"); + strlist_append(&left, "left again"); + strlist_append(&left, "left again"); + OMC_ASSERT(strlist_cmp(left, right) == 1, "should return 1"); + OMC_ASSERT(strlist_cmp(right, left) == 1, "should return 1"); + guard_strlist_free(&left); + guard_strlist_free(&right); +} + +void test_strlist_reverse() { + const char *data[] = { + "a", "b", "c", "d" + }; + const char *expected[] = { + "d", "c", "b", "a" + }; + struct StrList *list; + list = strlist_init(); + strlist_append_array(list, (char **) data); + + strlist_reverse(list); + int result = 0; + for (size_t i = 0; i < strlist_count(list); i++) { + char *item = strlist_item(list, i); + result += strcmp(item, expected[i]) == 0; + } + OMC_ASSERT(result == (int) strlist_count(list), "alphabetic sort failed"); + guard_strlist_free(&list); +} + +void test_strlist_count() { + struct StrList *list; + list = strlist_init(); + strlist_append(&list, "abc"); + strlist_append(&list, "123"); + strlist_append(&list, "def"); + strlist_append(&list, "456"); + OMC_ASSERT(strlist_count(NULL) == 0, "NULL input should produce a zero result"); + OMC_ASSERT(strlist_count(list) == 4, "incorrect record count"); + + guard_strlist_free(&list); +} + +void test_strlist_sort_alphabetic() { + const char *data[] = { + "b", "c", "d", "a" + }; + const char *expected[] = { + "a", "b", "c", "d" + }; + struct StrList *list; + list = strlist_init(); + strlist_append_array(list, (char **) data); + + strlist_sort(list, OMC_SORT_ALPHA); + int result = 0; + for (size_t i = 0; i < strlist_count(list); i++) { + char *item = strlist_item(list, i); + result += strcmp(item, expected[i]) == 0; + } + OMC_ASSERT(result == (int) strlist_count(list), "alphabetic sort failed"); + guard_strlist_free(&list); +} + +void test_strlist_sort_len_ascending() { + const char *data[] = { + "bb", "ccc", "dddd", "a" + }; + const char *expected[] = { + "a", "bb", "ccc", "dddd" + }; + struct StrList *list; + list = strlist_init(); + strlist_append_array(list, (char **) data); + + strlist_sort(list, OMC_SORT_LEN_ASCENDING); + int result = 0; + for (size_t i = 0; i < strlist_count(list); i++) { + char *item = strlist_item(list, i); + result += strcmp(item, expected[i]) == 0; + } + OMC_ASSERT(result == (int) strlist_count(list), "ascending sort failed"); + guard_strlist_free(&list); +} + +void test_strlist_sort_len_descending() { + const char *data[] = { + "bb", "ccc", "dddd", "a" + }; + const char *expected[] = { + "dddd", "ccc", "bb", "a" + }; + struct StrList *list; + list = strlist_init(); + strlist_append_array(list, (char **) data); + + strlist_sort(list, OMC_SORT_LEN_DESCENDING); + int result = 0; + for (size_t i = 0; i < strlist_count(list); i++) { + char *item = strlist_item(list, i); + result += strcmp(item, expected[i]) == 0; + } + OMC_ASSERT(result == (int) strlist_count(list), "descending sort failed"); + guard_strlist_free(&list); +} + +void test_strlist_item_as_str() { + struct StrList *list; + list = strlist_init(); + strlist_append(&list, "hello"); + OMC_ASSERT(strcmp(strlist_item_as_str(list, 0), "hello") == 0, "unexpected string result"); + guard_strlist_free(&list); +} + +void test_strlist_item_as_char() { + char result = 0; + struct testcase { + const char *data; + const char expected; + }; + struct testcase tc[] = { + {.data = "-129", .expected = 127}, // rollover + {.data = "-128", .expected = -128}, + {.data = "-1", .expected = -1}, + {.data = "1", .expected = 1}, + {.data = "127", .expected = 127}, + {.data = "128", .expected = -128}, // rollover + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_char, char); +} + +void test_strlist_item_as_uchar() { + int result; + struct testcase { + const char *data; + const unsigned char expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = 255}, + {.data = "1", .expected = 1}, + {.data = "255", .expected = 255}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_uchar, unsigned char); +} + +void test_strlist_item_as_short() { + int result; + struct testcase { + const char *data; + const short expected; + }; + struct testcase tc[] = { + {.data = "-32769", .expected = 32767}, // rollover + {.data = "-32768", .expected = -32768}, + {.data = "-1", .expected = -1}, + {.data = "1", .expected = 1}, + {.data = "32767", .expected = 32767}, + {.data = "32768", .expected = -32768}, // rollover + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_short, short); +} + +void test_strlist_item_as_ushort() { + int result; + struct testcase { + const char *data; + const unsigned short expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = 65535}, + {.data = "1", .expected = 1}, + {.data = "65535", .expected = 65535}, + {.data = "65536", .expected = 0}, // rollover + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_ushort, unsigned short); +} + +// From here on the values are different between architectures. Do very basic tests. +void test_strlist_item_as_int() { + int result; + struct testcase { + const char *data; + const int expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = -1}, + {.data = "1", .expected = 1}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_int, int); +} + +void test_strlist_item_as_uint() { + unsigned int result; + struct testcase { + const char *data; + const unsigned int expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = UINT_MAX}, + {.data = "1", .expected = 1}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_uint, unsigned int); +} + +void test_strlist_item_as_long() { + long result; + struct testcase { + const char *data; + const long expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = -1}, + {.data = "1", .expected = 1}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_long, long); +} + +void test_strlist_item_as_ulong() { + unsigned long result; + struct testcase { + const char *data; + const unsigned long expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = ULONG_MAX}, + {.data = "1", .expected = 1}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_ulong, unsigned long); +} + +void test_strlist_item_as_long_long() { + long long result; + struct testcase { + const char *data; + const long long expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = -1}, + {.data = "1", .expected = 1}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_long_long, long long); +} + +void test_strlist_item_as_ulong_long() { + unsigned long long result; + struct testcase { + const char *data; + const unsigned long long expected; + }; + struct testcase tc[] = { + {.data = "-1", .expected = ULLONG_MAX}, + {.data = "1", .expected = 1}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_ulong_long, unsigned long long); +} + +void test_strlist_item_as_float() { + float result; + struct testcase { + const char *data; + const float expected; + }; + struct testcase tc[] = { + {.data = "1.0", .expected = 1.0f}, + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_float, float); +} + +void test_strlist_item_as_double() { + double result; + struct testcase { + const char *data; + const double expected; + }; + struct testcase tc[] = { + {.data = "1.0", .expected = 1.0f}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_float, double); +} + +void test_strlist_item_as_long_double() { + long double result; + struct testcase { + const char *data; + const long double expected; + }; + struct testcase tc[] = { + {.data = "1.0", .expected = 1.0f}, + {.data = "abc", .expected = 0}, // error + }; + + BOILERPLATE_TEST_STRLIST_AS_TYPE(strlist_item_as_float, long double); +} + +int main(int argc, char *argv[]) { + OMC_TEST_BEGIN_MAIN(); + OMC_TEST_FUNC *tests[] = { + test_strlist_init, + test_strlist_free, + test_strlist_append, + test_strlist_append_many_records, + test_strlist_set, + test_strlist_append_file, + test_strlist_append_strlist, + test_strlist_append_tokenize, + test_strlist_append_array, + test_strlist_copy, + test_strlist_remove, + test_strlist_cmp, + test_strlist_sort_alphabetic, + test_strlist_sort_len_ascending, + test_strlist_sort_len_descending, + test_strlist_reverse, + test_strlist_count, + test_strlist_item_as_str, + test_strlist_item_as_char, + test_strlist_item_as_uchar, + test_strlist_item_as_short, + test_strlist_item_as_ushort, + test_strlist_item_as_int, + test_strlist_item_as_uint, + test_strlist_item_as_long, + test_strlist_item_as_ulong, + test_strlist_item_as_long_long, + test_strlist_item_as_ulong_long, + test_strlist_item_as_float, + test_strlist_item_as_double, + test_strlist_item_as_long_double, + }; + OMC_TEST_RUN(tests); + OMC_TEST_END_MAIN(); +}
\ No newline at end of file diff --git a/tests/test_system.c b/tests/test_system.c new file mode 100644 index 0000000..bf60222 --- /dev/null +++ b/tests/test_system.c @@ -0,0 +1,145 @@ +#include "testing.h" + +static int ascii_file_contains(const char *filename, const char *value) { + int result = -1; + char *contents = omc_testing_read_ascii(filename); + if (!contents) { + perror(filename); + return result; + } + result = strcmp(contents, value) == 0; + guard_free(contents); + return result; +} + +void test_shell_output_null_args() { + char *result; + int status; + result = shell_output(NULL, &status); + OMC_ASSERT(strcmp(result, "") == 0, "no output expected"); + OMC_ASSERT(status != 0, "expected a non-zero exit code due to null argument string"); +} + +void test_shell_output_non_zero_exit() { + char *result; + int status; + result = shell_output("HELLO1234 WORLD", &status); + OMC_ASSERT(strcmp(result, "") == 0, "no output expected"); + OMC_ASSERT(status != 0, "expected a non-zero exit code due to intentional error"); +} + +void test_shell_output() { + char *result; + int status; + result = shell_output("echo HELLO WORLD", &status); + OMC_ASSERT(strcmp(result, "HELLO WORLD\n") == 0, "output message was incorrect"); + OMC_ASSERT(status == 0, "expected zero exit code"); +} + +void test_shell_safe() { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + shell_safe(&proc, "true"); + OMC_ASSERT(proc.returncode == 0, "expected a zero exit code"); +} + +void test_shell_safe_verify_restrictions() { + struct Process proc; + + const char *invalid_chars = OMC_SHELL_SAFE_RESTRICT; + for (size_t i = 0; i < strlen(invalid_chars); i++) { + char cmd[PATH_MAX] = {0}; + memset(&proc, 0, sizeof(proc)); + + sprintf(cmd, "true%c false", invalid_chars[i]); + shell_safe(&proc, cmd); + OMC_ASSERT(proc.returncode == -1, "expected a negative result due to intentional error"); + } +} + +void test_shell_null_proc() { + int returncode = shell(NULL, "true"); + OMC_ASSERT(returncode == 0, "expected a zero exit code"); +} + +void test_shell_null_args() { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + shell(&proc, NULL); + OMC_ASSERT(proc.returncode < 0, "expected a non-zero/negative exit code"); +} + +void test_shell_exit() { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + shell(&proc, "true"); + OMC_ASSERT(proc.returncode == 0, "expected a zero exit code"); +} + +void test_shell_non_zero_exit() { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + shell(&proc, "false"); + OMC_ASSERT(proc.returncode != 0, "expected a non-zero exit code"); +} + +void test_shell() { + struct Process procs[] = { + {.f_stdout = "", .f_stderr = "", .redirect_stderr = 0, .returncode = 0}, + {.f_stdout = "stdout.log", .f_stderr = "", .redirect_stderr = 0, .returncode = 0}, + {.f_stdout = "", .f_stderr = "stderr.log", .redirect_stderr = 0, .returncode = 0}, + {.f_stdout = "stdouterr.log", .f_stderr = "", .redirect_stderr = 1, .returncode = 0}, + }; + for (size_t i = 0; i < sizeof(procs) / sizeof(*procs); i++) { + shell(&procs[i], "echo test_stdout; echo test_stderr >&2"); + struct Process *proc = &procs[i]; + switch (i) { + case 0: + OMC_ASSERT(proc->returncode == 0, "echo should not fail"); + break; + case 1: + OMC_ASSERT(proc->returncode == 0, "echo should not fail"); + OMC_ASSERT(access(proc->f_stdout, F_OK) == 0, "stdout.log should exist"); + OMC_ASSERT(access(proc->f_stderr, F_OK) != 0, "stderr.log should not exist"); + OMC_ASSERT(ascii_file_contains(proc->f_stdout, "test_stdout\n"), "output file did not contain test message"); + remove(proc->f_stdout); + break; + case 2: + OMC_ASSERT(proc->returncode == 0, "echo should not fail"); + OMC_ASSERT(access(proc->f_stdout, F_OK) != 0, "stdout.log should not exist"); + OMC_ASSERT(access(proc->f_stderr, F_OK) == 0, "stderr.log should exist"); + OMC_ASSERT(ascii_file_contains(proc->f_stderr, "test_stderr\n"), "output file did not contain test message"); + remove(proc->f_stderr); + break; + case 3: + OMC_ASSERT(proc->returncode == 0, "echo should not fail"); + OMC_ASSERT(access("stdouterr.log", F_OK) == 0, "stdouterr.log should exist"); + OMC_ASSERT(access("stderr.log", F_OK) != 0, "stdout.log should not exist"); + OMC_ASSERT(access("stderr.log", F_OK) != 0, "stderr.log should not exist"); + OMC_ASSERT(ascii_file_contains(proc->f_stdout, "test_stdout\ntest_stderr\n"), "output file did not contain test message"); + remove(proc->f_stdout); + remove(proc->f_stderr); + break; + default: + break; + } + } +} + +int main(int argc, char *argv[]) { + OMC_TEST_BEGIN_MAIN(); + OMC_TEST_FUNC *tests[] = { + test_shell_output_null_args, + test_shell_output_non_zero_exit, + test_shell_output, + test_shell_safe_verify_restrictions, + test_shell_safe, + test_shell_null_proc, + test_shell_null_args, + test_shell_non_zero_exit, + test_shell_exit, + test_shell, + }; + OMC_TEST_RUN(tests); + OMC_TEST_END_MAIN(); +} diff --git a/tests/test_template.c b/tests/test_template.c new file mode 100644 index 0000000..fda860a --- /dev/null +++ b/tests/test_template.c @@ -0,0 +1,100 @@ +#include "testing.h" + +extern void tpl_reset(); +extern struct tpl_item *tpl_pool[]; +extern unsigned tpl_pool_used; +extern unsigned tpl_pool_func_used; + + +static int adder(struct tplfunc_frame *frame, void *result) { + int a = (int) strtol(frame->argv[0].t_char_ptr, NULL, 10); + int b = (int) strtol(frame->argv[1].t_char_ptr, NULL, 10); + sprintf(result, "%d", a + b); + return 0; +} + +static int subtractor(struct tplfunc_frame *frame, void *result) { + int a = (int) strtol(frame->argv[0].t_char_ptr, NULL, 10); + int b = (int) strtol(frame->argv[1].t_char_ptr, NULL, 10); + sprintf(result, "%d", a - b); + return 0; +} + +static int multiplier(struct tplfunc_frame *frame, void *result) { + int a = (int) strtol(frame->argv[0].t_char_ptr, NULL, 10); + int b = (int) strtol(frame->argv[1].t_char_ptr, NULL, 10); + sprintf(result, "%d", a * b); + return 0; +} + +static int divider(struct tplfunc_frame *frame, void *result) { + int a = (int) strtol(frame->argv[0].t_char_ptr, NULL, 10); + int b = (int) strtol(frame->argv[1].t_char_ptr, NULL, 10); + sprintf(result, "%d", a / b); + return 0; +} + +void test_tpl_workflow() { + char *data = strdup("Hello world!"); + tpl_reset(); + tpl_register("hello_message", &data); + + OMC_ASSERT(strcmp(tpl_render("I said, \"{{ hello_message }}\""), "I said, \"Hello world!\"") == 0, "stored value in key is incorrect"); + setenv("HELLO", "Hello environment!", 1); + OMC_ASSERT(strcmp(tpl_render("{{ env:HELLO }}"), "Hello environment!") == 0, "environment variable content mismatch"); + unsetenv("HELLO"); + guard_free(data); +} + +void test_tpl_register() { + char *data = strdup("Hello world!"); + tpl_reset(); + unsigned used_before_register = tpl_pool_used; + tpl_register("hello_message", &data); + + OMC_ASSERT(tpl_pool_used == (used_before_register + 1), "tpl_register did not increment allocation counter"); + OMC_ASSERT(tpl_pool[used_before_register] != NULL, "register did not allocate a tpl_item record in the pool"); + free(data); +} + +void test_tpl_register_func() { + tpl_reset(); + struct tplfunc_frame tasks[] = { + {.key = "add", .argc = 2, .func = adder}, + {.key = "sub", .argc = 2, .func = subtractor}, + {.key = "mul", .argc = 2, .func = multiplier}, + {.key = "div", .argc = 2, .func = divider}, + }; + tpl_register_func("add", &tasks[0]); + tpl_register_func("sub", &tasks[1]); + tpl_register_func("mul", &tasks[2]); + tpl_register_func("div", &tasks[3]); + OMC_ASSERT(tpl_pool_func_used == sizeof(tasks) / sizeof(*tasks), "unexpected function pool used"); + + char *result = NULL; + result = tpl_render("{{ func:add(0,3) }}"); + OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + result = tpl_render("{{ func:add(1,2) }}"); + OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + result = tpl_render("{{ func:sub(6,3) }}"); + OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + result = tpl_render("{{ func:sub(4,1) }}"); + OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + result = tpl_render("{{ func:mul(1, 3) }}"); + OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + result = tpl_render("{{ func:div(6,2) }}"); + OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); + result = tpl_render("{{ func:div(3,1) }}"); + OMC_ASSERT(result != NULL && strcmp(result, "3") == 0, "Answer was not 3"); +} + +int main(int argc, char *argv[]) { + OMC_TEST_BEGIN_MAIN(); + OMC_TEST_FUNC *tests[] = { + test_tpl_workflow, + test_tpl_register_func, + test_tpl_register, + }; + OMC_TEST_RUN(tests); + OMC_TEST_END_MAIN(); +}
\ No newline at end of file diff --git a/tests/test_utils.c b/tests/test_utils.c new file mode 100644 index 0000000..cada76d --- /dev/null +++ b/tests/test_utils.c @@ -0,0 +1,477 @@ +#include "testing.h" + +char cwd_start[PATH_MAX]; +char cwd_workspace[PATH_MAX]; +extern char *dirstack[]; +extern const ssize_t dirstack_max; +extern ssize_t dirstack_len; + +void test_listdir() { + const char *dirs[] = { + "test_listdir", + "test_listdir/1", + "test_listdir/2", + "test_listdir/3", + }; + for (size_t i = 0; i < sizeof(dirs) / sizeof(*dirs); i++) { + mkdir(dirs[i], 0755); + } + struct StrList *listing; + listing = listdir(dirs[0]); + OMC_ASSERT(listing != NULL, "listdir failed"); + OMC_ASSERT(listing && (strlist_count(listing) == (sizeof(dirs) / sizeof(*dirs)) - 1), "should have 3"); + guard_strlist_free(&listing); +} + +void test_redact_sensitive() { + const char *data[] = { + "100 dollars!", + "bananas apples pears", + "have a safe trip", + }; + const char *to_redact[] = { + "dollars", + "bananas", + "have a safe trip", + NULL, + }; + const char *expected[] = { + "100 ***REDACTED***!", + "***REDACTED*** apples pears", + "***REDACTED***", + }; + + for (size_t i = 0; i < sizeof(data) / sizeof(*data); i++) { + char *input = strdup(data[i]); + char output[100] = {0}; + redact_sensitive(to_redact, input, output, sizeof(output) - 1); + OMC_ASSERT(strcmp(output, expected[i]) == 0, "incorrect redaction"); + } +} + +void test_fix_tox_conf() { + const char *filename = "tox.ini"; + const char *data = "[testenv]\n" + "key_before = 1\n" + "commands = pytest -sx tests\n" + "key_after = 2\n"; + const char *expected = "{posargs}\n"; + char *result = NULL; + FILE *fp; + + remove(filename); + fp = fopen(filename, "w"); + if (fp) { + fprintf(fp, "%s", data); + fclose(fp); + OMC_ASSERT(fix_tox_conf(filename, &result) == 0, "fix_tox_conf failed"); + } else { + OMC_ASSERT(false, "writing mock tox.ini failed"); + } + + char **lines = file_readlines(result, 0, 0, NULL); + OMC_ASSERT(strstr_array(lines, expected) != NULL, "{posargs} not found in result"); + + remove(result); + guard_free(result); +} + +void test_xml_pretty_print_in_place() { + FILE *fp; + const char *filename = "ugly.xml"; + const char *data = "<things><abc>123</abc><abc>321</abc></things>"; + const char *expected = "<?xml version=\"1.0\"?>\n" + "<things>\n" + " <abc>123</abc>\n" + " <abc>321</abc>\n" + "</things>\n"; + + remove(filename); + fp = fopen(filename, "w"); + if (fp) { + fprintf(fp, "%s", data); + fclose(fp); + OMC_ASSERT(xml_pretty_print_in_place( + filename, + OMC_XML_PRETTY_PRINT_PROG, + OMC_XML_PRETTY_PRINT_ARGS) == 0, + "xml pretty print failed (xmllint not installed?)"); + } else { + OMC_ASSERT(false, "failed to create input file"); + } + + fp = fopen(filename, "r"); + char buf[BUFSIZ] = {0}; + if (fread(buf, sizeof(*buf), sizeof(buf) - 1, fp) < 1) { + OMC_ASSERT(false, "failed to consume formatted xml file contents"); + } + OMC_ASSERT(strcmp(expected, buf) == 0, "xml file was not reformatted"); +} + +void test_path_store() { + char *dest = NULL; + chdir(cwd_workspace); + int result = path_store(&dest, PATH_MAX, cwd_workspace, "test_path_store"); + OMC_ASSERT(result == 0, "path_store encountered an error"); + OMC_ASSERT(dest != NULL, "dest should not be NULL"); + OMC_ASSERT(dest && (access(dest, F_OK) == 0), "destination path was not created"); + rmtree(dest); +} + +void test_isempty_dir() { + const char *dname = "test_isempty_dir"; + rmtree(dname); + mkdir(dname, 0755); + OMC_ASSERT(isempty_dir(dname) > 0, "empty directory went undetected"); + + char path[PATH_MAX]; + sprintf(path, "%s/file.txt", dname); + touch(path); + + OMC_ASSERT(isempty_dir(dname) == 0, "populated directory went undetected"); +} + +void test_xmkstemp() { + FILE *tempfp = NULL; + char *tempfile; + const char *data = "Hello, world!\n"; + + tempfile = xmkstemp(&tempfp, "w"); + OMC_ASSERT(tempfile != NULL, "failed to create temporary file"); + fprintf(tempfp, "%s", data); + fclose(tempfp); + + char buf[100] = {0}; + tempfp = fopen(tempfile, "r"); + fgets(buf, sizeof(buf) - 1, tempfp); + fclose(tempfp); + + OMC_ASSERT(strcmp(buf, data) == 0, "data written to temp file is incorrect"); + remove(tempfile); + free(tempfile); +} + +void test_msg() { + int flags_indent[] = { + 0, + OMC_MSG_L1, + OMC_MSG_L2, + OMC_MSG_L3, + }; + int flags_info[] = { + //OMC_MSG_NOP, + OMC_MSG_SUCCESS, + OMC_MSG_WARN, + OMC_MSG_ERROR, + //OMC_MSG_RESTRICT, + }; + for (size_t i = 0; i < sizeof(flags_indent) / sizeof(*flags_indent); i++) { + for (size_t x = 0; x < sizeof(flags_info) / sizeof(*flags_info); x++) { + msg(flags_info[x] | flags_indent[i], "Indent level %zu...Message %zu\n", i, x); + } + } +} + +void test_git_clone_and_describe() { + struct Process proc; + memset(&proc, 0, sizeof(proc)); + const char *local_workspace = "test_git_clone"; + const char *repo = "localrepo"; + const char *repo_git = "localrepo.git"; + char *cwd = getcwd(NULL, PATH_MAX); + + // remove the bare repo, and local clone + rmtree(repo); + rmtree(repo_git); + + mkdir(local_workspace, 0755); + chdir(local_workspace); + + // initialize a bare repo so we can clone it + char init_cmd[PATH_MAX]; + sprintf(init_cmd, "git init --bare %s", repo_git); + system(init_cmd); + + // clone the bare repo + OMC_ASSERT(git_clone(&proc, repo_git, repo, NULL) == 0, + "a local clone of bare repository should have succeeded"); + chdir(repo); + + // interact with it + touch("README.md"); + system("git add README.md"); + system("git commit --no-gpg-sign -m Test"); + system("git push -u origin"); + system("git --no-pager log"); + + // test git_describe is functional + char *taginfo_none = git_describe("."); + OMC_ASSERT(taginfo_none != NULL, "should be a git hash, not NULL"); + + system("git tag -a 1.0.0 -m Mock"); + system("git push --tags origin"); + char *taginfo = git_describe("."); + OMC_ASSERT(taginfo != NULL, "should be 1.0.0, not NULL"); + OMC_ASSERT(strcmp(taginfo, "1.0.0") == 0, "just-created tag was not described correctly"); + chdir(".."); + + char *taginfo_outer = git_describe(repo); + OMC_ASSERT(taginfo_outer != NULL, "should be 1.0.0, not NULL"); + OMC_ASSERT(strcmp(taginfo_outer, "1.0.0") == 0, "just-created tag was not described correctly (out-of-dir invocation)"); + + char *taginfo_bad = git_describe("abc1234_not_here_or_there"); + OMC_ASSERT(taginfo_bad == NULL, "a repository that shouldn't exist... exists and has a tag."); + chdir(cwd); +} + +void test_touch() { + OMC_ASSERT(touch("touchedfile.txt") == 0, "touch failed"); + OMC_ASSERT(access("touchedfile.txt", F_OK) == 0, "touched file does not exist"); + remove("touchedfile.txt"); +} + +void test_find_program() { + OMC_ASSERT(find_program("willnotexist123") == NULL, "did not return NULL"); + OMC_ASSERT(find_program("find") != NULL, "program not available (OS dependent)"); +} + +static int file_readlines_callback_modify(size_t size, char **line) { + char *data = (*line); + for (size_t i = 0; i < strlen(data); i++) { + if (isalnum(data[i])) { + data[i] = 'x'; + } + } + return 0; +} + +static int file_readlines_callback_get_specific_line(size_t size, char **line) { + char *data = (*line); + for (size_t i = 0; i < strlen(data); i++) { + if (!strcmp(data, "see?\n")) { + return 0; + } + } + return 1; +} + + +void test_file_readlines() { + const char *filename = "file_readlines.txt"; + const char *data = "I am\na file\nwith multiple lines\nsee?\n"; + FILE *fp = fopen(filename, "w"); + if (!fp) { + perror(filename); + return; + } + if (fwrite(data, sizeof(*data), strlen(data), fp) != strlen(data)) { + perror("short write"); + return; + } + fclose(fp); + + char **result = NULL; + result = file_readlines(filename, 0, 0, NULL); + int i; + for (i = 0; result[i] != NULL; i++); + OMC_ASSERT(num_chars(data, '\n') == i, "incorrect number of lines in data"); + OMC_ASSERT(strcmp(result[3], "see?\n") == 0, "last line in data is incorrect'"); + GENERIC_ARRAY_FREE(result); + + result = file_readlines(filename, 0, 0, file_readlines_callback_modify); + OMC_ASSERT(strcmp(result[3], "xxx?\n") == 0, "last line should be: 'xxx?\\n'"); + GENERIC_ARRAY_FREE(result); + + result = file_readlines(filename, 0, 0, file_readlines_callback_get_specific_line); + OMC_ASSERT(strcmp(result[0], "see?\n") == 0, "the first record of the result is not the last line of the file 'see?\\n'"); + GENERIC_ARRAY_FREE(result); + remove(filename); +} + +void test_path_dirname() { + const char *data[] = { + "a/b/c", "a/b", + "This/is/a/test", "This/is/a", + }; + for (size_t i = 0; i < sizeof(data) / sizeof(*data); i += 2) { + const char *input = data[i]; + const char *expected = data[i + 1]; + char tmp[PATH_MAX] = {0}; + strcpy(tmp, input); + + char *result = path_dirname(tmp); + OMC_ASSERT(strcmp(expected, result) == 0, NULL); + } +} + +void test_path_basename() { + const char *data[] = { + "a/b/c", "c", + "This/is/a/test", "test", + }; + for (size_t i = 0; i < sizeof(data) / sizeof(*data); i += 2) { + const char *input = data[i]; + const char *expected = data[i + 1]; + char *result = path_basename(input); + OMC_ASSERT(strcmp(expected, result) == 0, NULL); + } +} + +void test_expandpath() { + char *home; + + const char *homes[] = { + "HOME", + "USERPROFILE", + }; + for (size_t i = 0; i < sizeof(homes) / sizeof(*homes); i++) { + home = getenv(homes[i]); + if (home) { + break; + } + } + + char path[PATH_MAX] = {0}; + strcat(path, "~"); + strcat(path, DIR_SEP); + + char *expanded = expandpath(path); + OMC_ASSERT(startswith(expanded, home) > 0, expanded); + OMC_ASSERT(endswith(expanded, DIR_SEP) > 0, "the input string ends with a directory separator, the result did not"); + free(expanded); +} + +void test_rmtree() { + const char *root = "rmtree_dir"; + const char *tree[] = { + "abc", + "123", + "cba", + "321" + }; + chdir(cwd_workspace); + + mkdir(root, 0755); + for (size_t i = 0; i < sizeof(tree) / sizeof(*tree); i++) { + char path[PATH_MAX]; + char mockfile[PATH_MAX + 10]; + sprintf(path, "%s/%s", root, tree[i]); + sprintf(mockfile, "%s/%zu.txt", path, i); + if (mkdir(path, 0755)) { + perror(path); + OMC_ASSERT(false, NULL); + } + touch(mockfile); + } + OMC_ASSERT(rmtree(root) == 0, "rmtree should have been able to remove the directory"); + OMC_ASSERT(access(root, F_OK) < 0, "the directory is still present"); +} + +void test_dirstack() { + const char *data[] = { + "testdir", + "1", + "2", + "3", + }; + + char cwd[PATH_MAX]; + getcwd(cwd, PATH_MAX); + for (size_t i = 0; i < sizeof(data) / sizeof(*data); i++) { + mkdir(data[i], 0755); + pushd(data[i]); + getcwd(cwd, PATH_MAX); + } + OMC_ASSERT(dirstack_len == sizeof(data) / sizeof(*data), NULL); + OMC_ASSERT(strcmp(dirstack[0], cwd_workspace) == 0, NULL); + + for (int i = 1; i < dirstack_len; i++) { + OMC_ASSERT(endswith(dirstack[i], data[i - 1]), NULL); + } + + for (size_t i = 0, x = dirstack_len - 1; x != 0 && i < sizeof(data) / sizeof(*data); i++, x--) { + char *expected = strdup(dirstack[x]); + popd(); + getcwd(cwd, PATH_MAX); + OMC_ASSERT(strcmp(cwd, expected) == 0, NULL); + free(expected); + } +} + +void test_pushd_popd() { + const char *dname = "testdir"; + chdir(cwd_workspace); + rmtree(dname); + + OMC_ASSERT(mkdir(dname, 0755) == 0, "directory should not exist yet"); + OMC_ASSERT(pushd(dname) == 0, "failed to enter directory"); + char *cwd = getcwd(NULL, PATH_MAX); + + // we should be inside the test directory, not the starting directory + OMC_ASSERT(strcmp(cwd_workspace, cwd) != 0, ""); + OMC_ASSERT(popd() == 0, "return from directory failed"); + + char *cwd_after_popd = getcwd(NULL, PATH_MAX); + OMC_ASSERT(strcmp(cwd_workspace, cwd_after_popd) == 0, "should be the path where we started"); +} + +void test_pushd_popd_suggested_workflow() { + const char *dname = "testdir"; + chdir(cwd_workspace); + rmtree(dname); + + remove(dname); + if (!mkdir(dname, 0755)) { + if (!pushd(dname)) { + char *cwd = getcwd(NULL, PATH_MAX); + OMC_ASSERT(strcmp(cwd_workspace, cwd) != 0, NULL); + // return from dir + popd(); + free(cwd); + } + // cwd should be our starting directory + char *cwd = getcwd(NULL, PATH_MAX); + OMC_ASSERT(strcmp(cwd_workspace, cwd) == 0, NULL); + } else { + OMC_ASSERT(false, "mkdir function failed"); + } +} + + +int main(int argc, char *argv[]) { + OMC_TEST_BEGIN_MAIN(); + OMC_TEST_FUNC *tests[] = { + test_listdir, + test_redact_sensitive, + test_fix_tox_conf, + test_xml_pretty_print_in_place, + test_path_store, + test_isempty_dir, + test_xmkstemp, + test_msg, + test_git_clone_and_describe, + test_touch, + test_find_program, + test_file_readlines, + test_path_dirname, + test_path_basename, + test_expandpath, + test_rmtree, + test_dirstack, + test_pushd_popd, + test_pushd_popd_suggested_workflow, + }; + const char *ws = "workspace"; + getcwd(cwd_start, sizeof(cwd_start) - 1); + mkdir(ws, 0755); + chdir(ws); + getcwd(cwd_workspace, sizeof(cwd_workspace) - 1); + + OMC_TEST_RUN(tests); + + chdir(cwd_start); + if (rmtree(cwd_workspace)) { + perror(cwd_workspace); + } + OMC_TEST_END_MAIN(); +}
\ No newline at end of file diff --git a/tests/testing.h b/tests/testing.h new file mode 100644 index 0000000..2b5c1ba --- /dev/null +++ b/tests/testing.h @@ -0,0 +1,170 @@ +#ifndef OMC_TESTING_H +#define OMC_TESTING_H +#include "omc.h" +#define OMC_TEST_RUN_MAX 10000 +#define OMC_TEST_SUITE_FATAL 1 +#define OMC_TEST_SUITE_SKIP 127 + +typedef void(OMC_TEST_FUNC)(); +struct omc_test_result_t { + const char *filename; + const char *funcname; + int lineno; + const char *msg_assertion; + const char *msg_reason; + const int status; + const int skip; +} omc_test_results[OMC_TEST_RUN_MAX]; +size_t omc_test_results_i = 0; + +void omc_testing_record_result(struct omc_test_result_t result); + +void omc_testing_record_result(struct omc_test_result_t result) { + memcpy(&omc_test_results[omc_test_results_i], &result, sizeof(result)); + omc_test_results_i++; +} + +int omc_testing_has_failed() { + for (size_t i = 0; i < omc_test_results_i; i++) { + if (omc_test_results[i].status == false) { + return 1; + } + } + return 0; +} +void omc_testing_record_result_summary() { + size_t failed = 0; + size_t skipped = 0; + size_t passed = 0; + int do_message; + static char status_msg[255] = {0}; + for (size_t i = 0; i < omc_test_results_i; i++) { + if (omc_test_results[i].status && omc_test_results[i].skip) { + strcpy(status_msg, "SKIP"); + do_message = 1; + skipped++; + } else if (!omc_test_results[i].status) { + strcpy(status_msg, "FAIL"); + do_message = 1; + failed++; + } else { + strcpy(status_msg, "PASS"); + do_message = 0; + passed++; + } + fprintf(stdout, "[%s] %s:%d :: %s() => %s", + status_msg, + omc_test_results[i].filename, + omc_test_results[i].lineno, + omc_test_results[i].funcname, + omc_test_results[i].msg_assertion); + if (do_message) { + fprintf(stdout, "\n \\_ %s", omc_test_results[i].msg_reason); + } + fprintf(stdout, "\n"); + } + fprintf(stdout, "\n[UNIT] %zu tests passed, %zu tests failed, %zu tests skipped out of %zu\n", passed, failed, skipped, omc_test_results_i); +} + +char *omc_testing_read_ascii(const char *filename) { + struct stat st; + if (stat(filename, &st)) { + perror(filename); + return NULL; + } + + FILE *fp; + fp = fopen(filename, "r"); + if (!fp) { + perror(filename); + return NULL; + } + + char *result; + result = calloc(st.st_size + 1, sizeof(*result)); + if (fread(result, sizeof(*result), st.st_size, fp) < 1) { + perror(filename); + fclose(fp); + return NULL; + } + + fclose(fp); + return result; +} + +int omc_testing_write_ascii(const char *filename, const char *data) { + FILE *fp; + fp = fopen(filename, "w+"); + if (!fp) { + perror(filename); + return -1; + } + + if (!fprintf(fp, "%s", data)) { + perror(filename); + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +#define OMC_TEST_BEGIN_MAIN() do { \ + setenv("PYTHONUNBUFFERED", "1", 1); \ + fflush(stdout); \ + fflush(stderr); \ + setvbuf(stdout, NULL, _IONBF, 0); \ + setvbuf(stderr, NULL, _IONBF, 0); \ + atexit(omc_testing_record_result_summary); \ + } while (0) +#define OMC_TEST_END_MAIN() do { return omc_testing_has_failed(); } while (0) + +#define OMC_ASSERT(COND, REASON) do { \ + omc_testing_record_result((struct omc_test_result_t) { \ + .filename = __FILE_NAME__, \ + .funcname = __FUNCTION__, \ + .lineno = __LINE__, \ + .status = (COND), \ + .msg_assertion = "ASSERT(" #COND ")", \ + .msg_reason = REASON } ); \ + } while (0) + +#define OMC_ASSERT_FATAL(COND, REASON) do { \ + omc_testing_record_result((struct omc_test_result_t) { \ + .filename = __FILE_NAME__, \ + .funcname = __FUNCTION__, \ + .lineno = __LINE__, \ + .status = (COND), \ + .msg_assertion = "ASSERT FATAL (" #COND ")", \ + .msg_reason = REASON } \ + ); \ + if (omc_test_results[omc_test_results_i ? omc_test_results_i - 1 : omc_test_results_i].status == false) {\ + exit(OMC_TEST_SUITE_FATAL); \ + } \ + } while (0) + +#define OMC_SKIP_IF(COND, REASON) do { \ + omc_testing_record_result((struct omc_test_result_t) { \ + .filename = __FILE_NAME__, \ + .funcname = __FUNCTION__, \ + .lineno = __LINE__, \ + .status = true, \ + .skip = (COND), \ + .msg_assertion = "SKIP (" #COND ")", \ + .msg_reason = REASON } \ + ); \ + if (omc_test_results[omc_test_results_i ? omc_test_results_i - 1 : omc_test_results_i].skip == true) {\ + return; \ + } \ + } while (0) + +#define OMC_TEST_RUN(X) do { \ + for (size_t i = 0; i < sizeof(X) / sizeof(*X); i++) { \ + if (X[i]) { \ + X[i](); \ + } \ + } \ + } while (0) + +#endif //OMC_TESTING_H |