diff options
Diffstat (limited to 'tests/include/testing.h')
-rw-r--r-- | tests/include/testing.h | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/tests/include/testing.h b/tests/include/testing.h new file mode 100644 index 0000000..4c97bf2 --- /dev/null +++ b/tests/include/testing.h @@ -0,0 +1,254 @@ +#ifndef STASIS_TESTING_H +#define STASIS_TESTING_H +#include "core.h" +#define STASIS_TEST_RUN_MAX 10000 +#define STASIS_TEST_SUITE_FATAL 1 +#define STASIS_TEST_SUITE_SKIP 127 + +#ifndef __FILE_NAME__ +#define __FILE_NAME__ __FILE__ +#endif + +typedef void(STASIS_TEST_FUNC)(); +struct stasis_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; +} stasis_test_results[STASIS_TEST_RUN_MAX]; +size_t stasis_test_results_i = 0; + +extern inline void stasis_testing_setup_workspace(); +extern inline void stasis_testing_clean_up_docker(); +extern inline void stasis_testing_teardown_workspace(); +extern inline void stasis_testing_record_result(struct stasis_test_result_t result); +extern inline int stasis_testing_has_failed(); +extern inline void stasis_testing_record_result_summary(); +extern inline char *stasis_testing_read_ascii(const char *filename); +extern inline int stasis_testing_write_ascii(const char *filename, const char *data); + +inline void stasis_testing_record_result(struct stasis_test_result_t result) { + memcpy(&stasis_test_results[stasis_test_results_i], &result, sizeof(result)); + stasis_test_results_i++; +} + +inline int stasis_testing_has_failed() { + for (size_t i = 0; i < stasis_test_results_i; i++) { + if (stasis_test_results[i].status == false) { + return 1; + } + } + return 0; +} + +inline void stasis_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 < stasis_test_results_i; i++) { + if (stasis_test_results[i].status && stasis_test_results[i].skip) { + strcpy(status_msg, "SKIP"); + do_message = 1; + skipped++; + } else if (!stasis_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, + stasis_test_results[i].filename, + stasis_test_results[i].lineno, + stasis_test_results[i].funcname, + stasis_test_results[i].msg_assertion); + if (do_message) { + fprintf(stdout, "\n \\_ %s", stasis_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, stasis_test_results_i); +} + +inline char *stasis_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; +} + +inline int stasis_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; +} + +char TEST_DATA_DIR[PATH_MAX] = {0}; +char TEST_START_DIR[PATH_MAX] = {0}; +char TEST_WORKSPACE_DIR[PATH_MAX] = {0}; +inline void stasis_testing_setup_workspace() { + if (!realpath("data", TEST_DATA_DIR)) { + SYSERROR("%s", "Data directory is missing"); + exit(1); + } + + if (mkdir("workspaces", 0755) < 0) { + if (errno != EEXIST) { + SYSERROR("%s", "Unable to create workspaces directory"); + exit(1); + } + } + char ws[] = "workspaces/workspace_XXXXXX"; + if (mkdtemp(ws) == NULL) { + SYSERROR("Unable to create testing workspace: %s", ws); + exit(1); + } + if (!realpath(ws, TEST_WORKSPACE_DIR)) { + SYSERROR("%s", "Unable to determine absolute path to temporary workspace"); + exit(1); + } + if (chdir(TEST_WORKSPACE_DIR) < 0) { + SYSERROR("Unable to enter workspace directory: '%s'", TEST_WORKSPACE_DIR); + exit(1); + } + if (setenv("HOME", TEST_WORKSPACE_DIR, 1) < 0) { + SYSERROR("Unable to reset HOME to '%s'", TEST_WORKSPACE_DIR); + } +} + +inline void stasis_testing_clean_up_docker() { + char containers_dir[PATH_MAX] = {0}; + snprintf(containers_dir, sizeof(containers_dir) - 1, "%s/.local/share/containers", TEST_WORKSPACE_DIR); + + if (access(containers_dir, F_OK) == 0) { + char cmd[PATH_MAX] = {0}; + snprintf(cmd, sizeof(cmd) - 1, "docker run --rm -it -v %s:/data alpine sh -c '/bin/rm -r -f /data/*' &>/dev/null", containers_dir); + // This command will "fail" due to podman's internal protection(s). However, this gets us close enough. + system(cmd); + + // Podman seems to defer the rollback operation on-error for a short period. + // This buys time, so we can delete it. + sleep(1); + sync(); + if (rmtree(containers_dir)) { + SYSERROR("WARNING: Unable to fully remove container directory: '%s'", containers_dir); + } + } +} + +inline void stasis_testing_teardown_workspace() { + if (chdir(TEST_START_DIR) < 0) { + SYSERROR("Unable to re-enter test start directory from workspace directory: %s", TEST_START_DIR); + exit(1); + } + if (!getenv("KEEP_WORKSPACE")) { + if (strlen(TEST_WORKSPACE_DIR) > 1) { + stasis_testing_clean_up_docker(); + rmtree(TEST_WORKSPACE_DIR); + } + } +} + +#define STASIS_TEST_BEGIN_MAIN() do { \ + setenv("PYTHONUNBUFFERED", "1", 1); \ + fflush(stdout); \ + fflush(stderr); \ + setvbuf(stdout, NULL, _IONBF, 0); \ + setvbuf(stderr, NULL, _IONBF, 0); \ + if (!getcwd(TEST_START_DIR, sizeof(TEST_START_DIR) - 1)) { \ + SYSERROR("%s", "Unable to determine current working directory"); \ + exit(1); \ + } \ + atexit(stasis_testing_record_result_summary); \ + atexit(stasis_testing_teardown_workspace); \ + stasis_testing_setup_workspace(); \ + } while (0) +#define STASIS_TEST_END_MAIN() do { return stasis_testing_has_failed(); } while (0) + +#define STASIS_ASSERT(COND, REASON) do { \ + stasis_testing_record_result((struct stasis_test_result_t) { \ + .filename = __FILE_NAME__, \ + .funcname = __FUNCTION__, \ + .lineno = __LINE__, \ + .status = (COND), \ + .msg_assertion = "ASSERT(" #COND ")", \ + .msg_reason = REASON } ); \ + } while (0) + +#define STASIS_ASSERT_FATAL(COND, REASON) do { \ + stasis_testing_record_result((struct stasis_test_result_t) { \ + .filename = __FILE_NAME__, \ + .funcname = __FUNCTION__, \ + .lineno = __LINE__, \ + .status = (COND), \ + .msg_assertion = "ASSERT FATAL (" #COND ")", \ + .msg_reason = REASON } \ + ); \ + if (stasis_test_results[stasis_test_results_i ? stasis_test_results_i - 1 : stasis_test_results_i].status == false) {\ + exit(STASIS_TEST_SUITE_FATAL); \ + } \ + } while (0) + +#define STASIS_SKIP_IF(COND, REASON) do { \ + stasis_testing_record_result((struct stasis_test_result_t) { \ + .filename = __FILE_NAME__, \ + .funcname = __FUNCTION__, \ + .lineno = __LINE__, \ + .status = true, \ + .skip = (COND), \ + .msg_assertion = "SKIP (" #COND ")", \ + .msg_reason = REASON } \ + ); \ + if (stasis_test_results[stasis_test_results_i ? stasis_test_results_i - 1 : stasis_test_results_i].skip == true) {\ + return; \ + } \ + } while (0) + +#define STASIS_TEST_RUN(X) do { \ + for (size_t i = 0; i < sizeof(X) / sizeof(*X); i++) { \ + if (X[i]) { \ + X[i](); \ + } \ + } \ + } while (0) + +#endif //STASIS_TESTING_H |