diff options
-rw-r--r-- | CMakeLists.txt | 16 | ||||
-rw-r--r-- | include/delivery.h | 2 | ||||
-rw-r--r-- | include/multiprocessing.h | 3 | ||||
-rw-r--r-- | src/cli/stasis/stasis_main.c | 17 | ||||
-rw-r--r-- | src/lib/core/conda.c | 15 | ||||
-rw-r--r-- | src/lib/core/delivery.c | 14 | ||||
-rw-r--r-- | src/lib/core/delivery_init.c | 2 | ||||
-rw-r--r-- | src/lib/core/multiprocessing.c | 12 | ||||
-rw-r--r-- | tests/CMakeLists.txt | 28 | ||||
-rw-r--r-- | tests/test_conda.c | 61 | ||||
-rw-r--r-- | tests/test_envctl.c | 72 | ||||
-rw-r--r-- | tests/test_multiprocessing.c | 92 |
12 files changed, 298 insertions, 36 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index caed929..6558205 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,14 +26,30 @@ endif() add_subdirectory(src) +# Toggle extremely verbose output option(BUILD_TESTING_DEBUG OFF) if (BUILD_TESTING_DEBUG) add_compile_options(-DDEBUG) endif() + +# Toggle regression testing on/off +option(BUILD_TESTING_RT ON) + +# Toggle testing option(BUILD_TESTING OFF) +message(CHECK_START "Run unit tests") if (BUILD_TESTING) + message(CHECK_PASS "yes") enable_testing() + message(CHECK_START "Run regression tests") + if (BUILD_TESTING_RT) + message(CHECK_PASS "yes") + else() + message(CHECK_PASS "no") + endif() add_subdirectory(tests) +else() + message(CHECK_PASS "no") endif() set(SYSCONFDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_SYSCONFDIR}") diff --git a/include/delivery.h b/include/delivery.h index bd5137c..345cd13 100644 --- a/include/delivery.h +++ b/include/delivery.h @@ -385,7 +385,7 @@ void delivery_install_conda(char *install_script, char *conda_install_dir); int delivery_format_str(struct Delivery *ctx, char **dest, const char *fmt); // helper function -void delivery_gather_tool_versions(struct Delivery *ctx); +int delivery_gather_tool_versions(struct Delivery *ctx); // helper function int delivery_init_tmpdir(struct Delivery *ctx); diff --git a/include/multiprocessing.h b/include/multiprocessing.h index 5919462..ec7c1ad 100644 --- a/include/multiprocessing.h +++ b/include/multiprocessing.h @@ -38,6 +38,9 @@ struct MultiProcessingPool { int status_interval; ///< Report a pooled task is "running" every n seconds }; +/// A multiprocessing task's initial state (i.e. "FAIL") +#define MP_POOL_TASK_STATUS_INITIAL (-1) + /// Maximum number of multiprocessing tasks STASIS can execute #define MP_POOL_TASK_MAX 1000 diff --git a/src/cli/stasis/stasis_main.c b/src/cli/stasis/stasis_main.c index 5325892..c2443d7 100644 --- a/src/cli/stasis/stasis_main.c +++ b/src/cli/stasis/stasis_main.c @@ -332,14 +332,15 @@ int main(int argc, char *argv[]) { exit(1); } - delivery_gather_tool_versions(&ctx); - if (!ctx.conda.tool_version) { - msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda version\n"); - exit(1); - } - if (!ctx.conda.tool_build_version) { - msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda-build version\n"); - exit(1); + if (delivery_gather_tool_versions(&ctx)) { + if (!ctx.conda.tool_version) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda version\n"); + exit(1); + } + if (!ctx.conda.tool_build_version) { + msg(STASIS_MSG_ERROR | STASIS_MSG_L2, "Could not determine conda-build version\n"); + exit(1); + } } if (pip_exec("install build")) { diff --git a/src/lib/core/conda.c b/src/lib/core/conda.c index 35caf02..25069f8 100644 --- a/src/lib/core/conda.c +++ b/src/lib/core/conda.c @@ -109,7 +109,7 @@ int pip_index_provides(const char *index_url, const char *spec) { strcpy(proc.f_stdout, logfile); // Do an installation in dry-run mode to see if the package exists in the given index. - snprintf(cmd, sizeof(cmd) - 1, "python -m pip install --dry-run --no-deps --index-url=%s %s", index_url, spec_local); + snprintf(cmd, sizeof(cmd) - 1, "python -m pip install --dry-run --no-cache --no-deps --index-url=%s '%s'", index_url, spec_local); status = shell(&proc, cmd); // Print errors only when shell() itself throws one @@ -443,6 +443,15 @@ char *conda_get_active_environment() { int conda_provides(const char *spec) { struct Process proc; memset(&proc, 0, sizeof(proc)); + + // Short circuit: + // Running "mamba search" without an argument will print every package in + // all channels, then return "found". Prevent this. + // No data implies "not found". + if (isempty((char *) spec)) { + return 0; + } + strcpy(proc.f_stdout, "/dev/null"); strcpy(proc.f_stderr, "/dev/null"); @@ -450,12 +459,12 @@ int conda_provides(const char *spec) { // conda_exec() expects the program output to be visible to the user. // For this operation we only need the exit value. char cmd[PATH_MAX] = {0}; - snprintf(cmd, sizeof(cmd) - 1, "mamba search --use-index-cache %s", spec); + snprintf(cmd, sizeof(cmd) - 1, "mamba search %s", spec); if (shell(&proc, cmd) < 0) { fprintf(stderr, "shell: %s", strerror(errno)); return -1; } - return proc.returncode == 0; + return proc.returncode == 0; // 0=not_found, 1=found } int conda_index(const char *path) { diff --git a/src/lib/core/delivery.c b/src/lib/core/delivery.c index e32ed4c..5645dcc 100644 --- a/src/lib/core/delivery.c +++ b/src/lib/core/delivery.c @@ -302,16 +302,22 @@ void delivery_defer_packages(struct Delivery *ctx, int type) { } } -void delivery_gather_tool_versions(struct Delivery *ctx) { - int status = 0; +int delivery_gather_tool_versions(struct Delivery *ctx) { + int status_tool_version = 0; + int status_tool_build_version = 0; // Extract version from tool output - ctx->conda.tool_version = shell_output("conda --version", &status); + ctx->conda.tool_version = shell_output("conda --version", &status_tool_version); if (ctx->conda.tool_version) strip(ctx->conda.tool_version); - ctx->conda.tool_build_version = shell_output("conda build --version", &status); + ctx->conda.tool_build_version = shell_output("conda build --version", &status_tool_build_version); if (ctx->conda.tool_build_version) strip(ctx->conda.tool_version); + + if (status_tool_version || status_tool_build_version) { + return 1; + } + return 0; } diff --git a/src/lib/core/delivery_init.c b/src/lib/core/delivery_init.c index e914f99..2333628 100644 --- a/src/lib/core/delivery_init.c +++ b/src/lib/core/delivery_init.c @@ -257,7 +257,7 @@ int delivery_init(struct Delivery *ctx, int render_mode) { char cache_local[PATH_MAX]; sprintf(cache_local, "%s/%s", ctx->storage.tmpdir, "cache"); - setenv("XDG_CACHE_HOME", ctx->storage.tmpdir, 1); + setenv("XDG_CACHE_HOME", cache_local, 1); // add tools to PATH char pathvar_tmp[STASIS_BUFSIZ]; diff --git a/src/lib/core/multiprocessing.c b/src/lib/core/multiprocessing.c index 484c566..252bab9 100644 --- a/src/lib/core/multiprocessing.c +++ b/src/lib/core/multiprocessing.c @@ -98,7 +98,7 @@ struct MultiProcessingTask *mp_pool_task(struct MultiProcessingPool *pool, const } // Set default status to "error" - slot->status = -1; + slot->status = MP_POOL_TASK_STATUS_INITIAL; // Set task identifier string memset(slot->ident, 0, sizeof(slot->ident)); @@ -169,7 +169,13 @@ void mp_pool_show_summary(struct MultiProcessingPool *pool) { for (size_t i = 0; i < pool->num_used; i++) { struct MultiProcessingTask *task = &pool->task[i]; char status_str[10] = {0}; - if (!task->status && !task->signaled_by) { + + if (task->status == MP_POOL_TASK_STATUS_INITIAL && task->pid == MP_POOL_PID_UNUSED) { + // You will only see this label if the task pool is killed by + // MP_POOL_FAIL_FAST and tasks are still queued for execution + strcpy(status_str, "HOLD"); + } else if (!task->status && !task->signaled_by) { + strcpy(status_str, "DONE"); } else if (task->signaled_by) { strcpy(status_str, "TERM"); @@ -254,7 +260,7 @@ int mp_pool_join(struct MultiProcessingPool *pool, size_t jobs, size_t flags) { for (size_t i = lower_i; i < upper_i; i++) { struct MultiProcessingTask *slot = &pool->task[i]; - if (slot->status == -1) { + if (slot->status == MP_POOL_TASK_STATUS_INITIAL) { if (mp_task_fork(pool, slot)) { fprintf(stderr, "%s: mp_task_fork failed\n", slot->ident); kill(0, SIGTERM); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f4380e0..0da290f 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -12,30 +12,30 @@ set(win_msvc_cflags /Wall) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/data/generic.ini ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/data/result.xml ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/data/result_error.xml ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) -file(GLOB files "test_*.c") +file(GLOB source_files "test_*.c") -if (BASH_PROGRAM) +if (BASH_PROGRAM AND BUILD_TESTING_RT) add_test (rt_generic ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/rt_generic.sh) endif() -foreach(file ${files}) - string(REGEX REPLACE "(^.*/|\\.[^.]*$)" "" file_without_ext ${file}) - add_executable(${file_without_ext} ${file}) +foreach(source_file ${source_files}) + string(REGEX REPLACE "(^.*/|\\.[^.]*$)" "" test_executable ${source_file}) + add_executable(${test_executable} ${source_file}) if (CMAKE_C_COMPILER_ID STREQUAL "GNU") - target_compile_options(${file_without_ext} PRIVATE ${nix_cflags} ${nix_gnu_cflags}) + target_compile_options(${test_executable} PRIVATE ${nix_cflags} ${nix_gnu_cflags}) elseif (CMAKE_C_COMPILER_ID MATCHES "Clang") - target_compile_options(${file_without_ext} PRIVATE ${nix_cflags} ${nix_clang_cflags}) + target_compile_options(${test_executable} PRIVATE ${nix_cflags} ${nix_clang_cflags}) elseif (CMAKE_C_COMPILER_ID STREQUAL "MSVC") - target_compile_options(${file_without_ext} PRIVATE ${win_cflags} ${win_msvc_cflags}) + target_compile_options(${test_executable} PRIVATE ${win_cflags} ${win_msvc_cflags}) endif() - target_link_libraries(${file_without_ext} PRIVATE stasis_core) - add_test(${file_without_ext} ${file_without_ext}) - set_tests_properties(${file_without_ext} + target_link_libraries(${test_executable} PRIVATE stasis_core) + add_test(${test_executable} ${test_executable}) + set_tests_properties(${test_executable} PROPERTIES - TIMEOUT 120) - set_tests_properties(${file_without_ext} + TIMEOUT 240) + set_tests_properties(${test_executable} PROPERTIES SKIP_RETURN_CODE 127) - set_property(TEST ${file_without_ext} + set_property(TEST ${test_executable} PROPERTY ENVIRONMENT "STASIS_SYSCONFDIR=${CMAKE_SOURCE_DIR}") endforeach()
\ No newline at end of file diff --git a/tests/test_conda.c b/tests/test_conda.c index 2ed869a..9ad12c8 100644 --- a/tests/test_conda.c +++ b/tests/test_conda.c @@ -126,6 +126,57 @@ void test_conda_index() { STASIS_ASSERT(conda_index("channel") == 0, "cannot index a simple conda channel"); } +void test_pip_index_provides() { + struct testcase { + const char *pindex; + const char *name; + int expected; + }; + struct testcase tc[] = { + {.pindex = PYPI_INDEX_DEFAULT, .name = "firewatch", .expected = 1}, + {.pindex = PYPI_INDEX_DEFAULT, .name = "doesnotexistfirewatch", .expected = 0}, + {.pindex = "bad_index", .name = "firewatch", .expected = 0}, + {.pindex = PYPI_INDEX_DEFAULT, .name = "", .expected = -1}, + {.pindex = "", .name = "", .expected = -1}, + }; + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + struct testcase *test = &tc[i]; + int result = pip_index_provides(test->pindex, test->name) ; + STASIS_ASSERT(result == test->expected, "Unexpected result"); + } +} + +void test_conda_get_active_environment() { + conda_activate(ctx.storage.conda_install_prefix, "base"); + STASIS_ASSERT(strcmp(conda_get_active_environment(), "base") == 0, "base environment not active"); +} + +void test_conda_provides() { + struct testcase { + const char *name; + int expected; + }; + struct testcase tc[] = { + {.name = "fitsverify", .expected = 1}, + {.name = "doesnotexistfitsverify", .expected = 0}, + {.name = "", .expected = 0}, + }; + + for (size_t i = 0; i < sizeof(tc) / sizeof(*tc); i++) { + struct testcase *test = &tc[i]; + int result = conda_provides(test->name); + printf("%s returned %d, expecting %d\n", test->name, result, test->expected); + STASIS_ASSERT(result == test->expected, "Unexpected result"); + } +} + +void test_delivery_gather_tool_versions() { + int status = delivery_gather_tool_versions(&ctx); + STASIS_ASSERT(status == 0, "Failed to gather tool versions"); + STASIS_ASSERT(!isempty(ctx.conda.tool_version), "conda version is empty"); + STASIS_ASSERT(!isempty(ctx.conda.tool_build_version), "conda_build version is empty"); +} + int main(int argc, char *argv[]) { STASIS_TEST_BEGIN_MAIN(); STASIS_TEST_FUNC *tests[] = { @@ -133,11 +184,15 @@ int main(int argc, char *argv[]) { test_conda_installation, test_conda_activate, test_conda_setup_headless, + test_conda_provides, + test_conda_get_active_environment, test_conda_exec, test_python_exec, test_conda_env_create_from_uri, test_conda_env_create_export_remove, test_conda_index, + test_pip_index_provides, + test_delivery_gather_tool_versions, }; const char *ws = "workspace"; @@ -165,8 +220,8 @@ int main(int argc, char *argv[]) { ctx._stasis_ini_fp.delivery = ini; ctx._stasis_ini_fp.delivery_path = realpath("mock.ini", NULL); - setenv("TMPDIR", cwd_workspace, 1); - globals.sysconfdir = getenv("STASIS_SYSCONFDIR"); + const char *sysconfdir = getenv("STASIS_SYSCONFDIR"); + globals.sysconfdir = strdup(sysconfdir ? sysconfdir : STASIS_SYSCONFDIR); ctx.storage.root = strdup(cwd_workspace); setenv("LANG", "C", 1); @@ -179,5 +234,7 @@ int main(int argc, char *argv[]) { if (rmtree(cwd_workspace)) { perror(cwd_workspace); } + delivery_free(&ctx); + globals_free(); STASIS_TEST_END_MAIN(); }
\ No newline at end of file diff --git a/tests/test_envctl.c b/tests/test_envctl.c new file mode 100644 index 0000000..eab0186 --- /dev/null +++ b/tests/test_envctl.c @@ -0,0 +1,72 @@ +#include "testing.h" +#include "envctl.h" + +void test_envctl_init() { + struct EnvCtl *envctl; + STASIS_ASSERT_FATAL((envctl = envctl_init()) != NULL, "envctl could not be initialized"); + STASIS_ASSERT(envctl->num_alloc == STASIS_ENVCTL_DEFAULT_ALLOC, "freshly initialized envctl does not have the correct number records"); + STASIS_ASSERT(envctl->num_used == 0, "freshly initialized envctl should have no allocations in use"); + STASIS_ASSERT(envctl->item != NULL, "freshly initialized envctl should have an empty items array. this one is NULL."); + STASIS_ASSERT(envctl->item[0] == NULL, "freshly initialized envctl should not have any items. this one does."); + envctl_free(&envctl); + STASIS_ASSERT(envctl == NULL, "envctl should be NULL after envctl_free()"); +} + +static int except_passthru(const void *a, const void *b) { + const struct EnvCtl_Item *item = a; + const char *name = b; + if (!envctl_check_required(item->flags) && envctl_check_present(item, name)) { + return STASIS_ENVCTL_RET_SUCCESS; + } + return STASIS_ENVCTL_RET_FAIL; +} + +static int except_required(const void *a, const void *b) { + const struct EnvCtl_Item *item = a; + const char *name = b; + if (envctl_check_required(item->flags) && envctl_check_present(item, name)) { + return STASIS_ENVCTL_RET_SUCCESS; + } + return STASIS_ENVCTL_RET_FAIL; +} + +static int except_redact(const void *a, const void *b) { + const struct EnvCtl_Item *item = a; + const char *name = b; + if (envctl_check_redact(item->flags) && envctl_check_present(item, name)) { + return STASIS_ENVCTL_RET_SUCCESS; + } + return STASIS_ENVCTL_RET_FAIL; +} + +void test_envctl_register() { + struct EnvCtl *envctl; + envctl = envctl_init(); + setenv("passthru", "true", 1); + setenv("required", "true", 1); + setenv("redact", "true", 1); + envctl_register(&envctl, STASIS_ENVCTL_PASSTHRU, except_passthru, "passthru"); + envctl_register(&envctl, STASIS_ENVCTL_REQUIRED, except_required, "required"); + envctl_register(&envctl, STASIS_ENVCTL_REDACT, except_redact, "redact"); + + unsigned flags[] = { + STASIS_ENVCTL_PASSTHRU, + STASIS_ENVCTL_REQUIRED, + STASIS_ENVCTL_REDACT, + }; + for (size_t i = 0; i < envctl->num_used; i++) { + struct EnvCtl_Item *item = envctl->item[i]; + STASIS_ASSERT(item->flags == flags[i], "incorrect flag for item"); + } + envctl_free(&envctl); +} + +int main(int argc, char *argv[]) { + STASIS_TEST_BEGIN_MAIN(); + STASIS_TEST_FUNC *tests[] = { + test_envctl_init, + test_envctl_register, + }; + STASIS_TEST_RUN(tests); + STASIS_TEST_END_MAIN(); +}
\ No newline at end of file diff --git a/tests/test_multiprocessing.c b/tests/test_multiprocessing.c index b9cd309..7c9d695 100644 --- a/tests/test_multiprocessing.c +++ b/tests/test_multiprocessing.c @@ -1,5 +1,6 @@ #include "testing.h" #include "multiprocessing.h" +#include <pthread.h> static struct MultiProcessingPool *pool; char *commands[] = { @@ -12,6 +13,10 @@ char *commands[] = { }; void test_mp_pool_init() { + STASIS_ASSERT((pool = mp_pool_init(NULL, "mplogs")) == NULL, "Pool should not be initialized with invalid ident"); + STASIS_ASSERT((pool = mp_pool_init("mypool", NULL)) == NULL, "Pool should not be initialized with invalid logname"); + STASIS_ASSERT((pool = mp_pool_init(NULL, NULL)) == NULL, "Pool should not be initialized with invalid arguments"); + pool = NULL; STASIS_ASSERT((pool = mp_pool_init("mypool", "mplogs")) != NULL, "Pool initialization failed"); STASIS_ASSERT_FATAL(pool != NULL, "Should not be NULL"); STASIS_ASSERT(pool->num_alloc == MP_POOL_TASK_MAX, "Wrong number of default records"); @@ -56,6 +61,7 @@ void test_mp_task() { pool = mp_pool_init("mypool", "mplogs"); if (pool) { + pool->status_interval = 3; for (size_t i = 0; i < sizeof(commands) / sizeof(*commands); i++) { struct MultiProcessingTask *task; char task_name[100] = {0}; @@ -113,6 +119,90 @@ void test_mp_pool_workflow() { } } +void test_mp_fail_fast() { + char *commands_ff[128] = { + "sleep 3; true", + "sleep 5; false", + }; + + // Pad the array with tasks. None of these should execute when + // the "fail fast" conditions are met + char *nopcmd = "sleep 30; true"; + for (size_t i = 2; i < sizeof(commands_ff) / sizeof(*commands_ff); i++) { + commands_ff[i] = nopcmd; + } + + struct MultiProcessingPool *p; + STASIS_ASSERT((p = mp_pool_init("failfast", "failfastlogs")) != NULL, "Failed to initialize pool"); + for (size_t i = 0; i < sizeof(commands_ff) / sizeof(*commands_ff); i++) { + char *command = commands_ff[i]; + char taskname[100] = {0}; + snprintf(taskname, sizeof(taskname) - 1, "task_%03zu", i); + STASIS_ASSERT(mp_pool_task(p, taskname, NULL, (char *) command) != NULL, "Failed to queue task"); + } + + STASIS_ASSERT(mp_pool_join(p, get_cpu_count(), MP_POOL_FAIL_FAST) < 0, "Unexpected result"); + + struct result { + int total_signaled; + int total_status_fail; + int total_status_success; + int total_unused; + } result = { + .total_signaled = 0, + .total_status_fail = 0, + .total_status_success = 0, + .total_unused = 0, + }; + for (size_t i = 0; i < p->num_used; i++) { + struct MultiProcessingTask *task = &p->task[i]; + if (task->signaled_by) result.total_signaled++; + if (task->status > 0) result.total_status_fail++; + if (task->status == 0) result.total_status_success++; + if (task->pid == MP_POOL_PID_UNUSED && task->status == MP_POOL_TASK_STATUS_INITIAL) result.total_unused++; + } + fprintf(stderr, "total_status_fail = %d\ntotal_status_success = %d\ntotal_signaled = %d\ntotal_unused = %d\n", + result.total_status_fail, result.total_status_success, result.total_signaled, result.total_unused); + STASIS_ASSERT(result.total_status_fail, "Should have failures"); + STASIS_ASSERT(result.total_status_success, "Should have successes"); + STASIS_ASSERT(result.total_signaled, "Should have signaled PIDs"); + STASIS_ASSERT(result.total_unused, "Should have PIDs marked UNUSED."); + mp_pool_show_summary(p); + mp_pool_free(&p); +} + +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static void *pool_container(void *data) { + char *commands_sc[] = { + "sleep 10; echo done sleeping" + }; + struct MultiProcessingPool **x = (struct MultiProcessingPool **) data; + struct MultiProcessingPool *p = (*x); + pthread_mutex_lock(&mutex); + mp_pool_task(p, "stop_resume_test", NULL, commands_sc[0]); + mp_pool_join(p, 1, 0); + mp_pool_show_summary(p); + mp_pool_free(&p); + pthread_mutex_unlock(&mutex); + return NULL; +} + +void test_mp_stop_continue() { + struct MultiProcessingPool *p = NULL; + STASIS_ASSERT((p = mp_pool_init("stopcontinue", "stopcontinuelogs")) != NULL, "Failed to initialize pool"); + pthread_t th; + pthread_create(&th, NULL, pool_container, &p); + sleep(2); + if (p->task[0].pid != MP_POOL_PID_UNUSED) { + STASIS_ASSERT(kill(p->task[0].pid, SIGSTOP) == 0, "SIGSTOP failed"); + sleep(2); + STASIS_ASSERT(kill(p->task[0].pid, SIGCONT) == 0, "SIGCONT failed"); + } else { + STASIS_ASSERT(false, "Task was marked as unused when it shouldn't have been"); + } + pthread_join(th, NULL); +} + int main(int argc, char *argv[]) { STASIS_TEST_BEGIN_MAIN(); STASIS_TEST_FUNC *tests[] = { @@ -121,6 +211,8 @@ int main(int argc, char *argv[]) { test_mp_pool_join, test_mp_pool_free, test_mp_pool_workflow, + test_mp_fail_fast, + test_mp_stop_continue }; STASIS_TEST_RUN(tests); STASIS_TEST_END_MAIN(); |