labfy-investigation/tests/test_local_tool_runner.c
fy59 d65f9559e6 chore(checkpoint): preserve local investigation workbench
Preserve the local graph-first investigation stack through macro V1.

Include reviewed source, tests, contracts, documentation and synthetic assets.

Retain the standalone financial V21 test foundation without runtime migration.

Runtime remains V20; operational JobStore remains V3.

Local experimental checkpoint, not a stable release.
2026-09-27 08:34:46 +02:00

179 lines
8.4 KiB
C

/******************************************************************************
* @file test_local_tool_runner.c
* @brief Régressions du runner POSIX borné J4.
******************************************************************************/
#include "core/local_tool_runner.h"
#include <glib.h>
#include <glib/gstdio.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <unistd.h>
static LocalToolRunnerProfile profile(void)
{
return (LocalToolRunnerProfile) { 500U, 50U, 2U,
128U * 1024U * 1024U, 1024U * 1024U, 32U, 4096U, 4096U };
}
static char *fake_path(void)
{
char *relative = g_canonicalize_filename("tests/fake_document_tool", NULL);
g_assert_true(g_path_is_absolute(relative));
return relative;
}
static void test_binary_and_nonzero(void)
{
char *fake = fake_path(); char *work = g_dir_make_tmp("labfy-local-run-XXXXXX", NULL);
const char *binary[] = {fake, "--binary", NULL};
LocalToolRunnerResult *result = NULL; LocalToolRunnerProfile limits = profile();
g_assert_true(local_tool_runner_run(fake, binary, work, &limits,
NULL, &result, NULL));
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_EXITED);
g_assert_cmpuint(g_bytes_get_size(result->stdout_bytes), ==, 4U);
g_assert_true(result->stdout_complete); local_tool_runner_result_free(result);
const char *nonzero[] = {fake, "--emit", "--exit-status", "7", NULL};
result = NULL; g_assert_true(local_tool_runner_run(fake, nonzero, work,
&limits, NULL, &result, NULL));
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_NONZERO);
g_assert_cmpint(result->exit_code, ==, 7);
local_tool_runner_result_free(result); g_rmdir(work); g_free(work); g_free(fake);
}
static void test_output_limit_and_concurrent(void)
{
char *fake = fake_path(); char *work = g_dir_make_tmp("labfy-local-run-XXXXXX", NULL);
const char *argv[] = {fake, "--emit", "--stdout-size", "12000",
"--stderr-size", "12000", "--chunks", "30", "--slow", NULL};
LocalToolRunnerProfile limits = profile(); limits.stdout_bytes = 1000U;
limits.stderr_bytes = 1200U; LocalToolRunnerResult *result = NULL;
g_assert_true(local_tool_runner_run(fake, argv, work, &limits,
NULL, &result, NULL));
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_OUTPUT_LIMIT);
g_assert_cmpuint(g_bytes_get_size(result->stdout_bytes), <=, 1000U);
g_assert_cmpuint(g_bytes_get_size(result->stderr_bytes), <=, 1200U);
g_assert_false(result->stdout_complete && result->stderr_complete);
local_tool_runner_result_free(result); g_rmdir(work); g_free(work); g_free(fake);
}
static gpointer cancel_later(gpointer data)
{ g_usleep(50000U); g_cancellable_cancel(data); return NULL; }
static gpointer cancel_process_after_start(gpointer data)
{
const char *marker = data;
for (guint attempt = 0; attempt < 200U; attempt++) {
if (g_file_test(marker, G_FILE_TEST_IS_REGULAR)) break;
g_usleep(5000U);
}
g_assert_true(g_file_test(marker, G_FILE_TEST_IS_REGULAR));
local_tool_runner_request_process_cancel();
return NULL;
}
static void test_process_signal_cancellation_after_start(void)
{
char *fake = fake_path();
char *work = g_dir_make_tmp("labfy-local-cancel-XXXXXX", NULL);
char *marker = g_build_filename(work, "started", NULL);
const char *argv[] = {fake, "--sleep", "--ignore-term", "--started-file",
marker, NULL};
LocalToolRunnerProfile limits = profile(); limits.wall_timeout_ms = 3000U;
LocalToolRunnerResult *result = NULL;
local_tool_runner_reset_process_cancel();
GThread *thread = g_thread_new("cancel-process", cancel_process_after_start,
marker);
g_assert_true(local_tool_runner_run(fake, argv, work, &limits, NULL,
&result, NULL));
g_thread_join(thread);
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_CANCELLED);
g_assert_cmpint(result->elapsed_us, <, 1500000);
local_tool_runner_reset_process_cancel();
local_tool_runner_result_free(result); g_remove(marker); g_rmdir(work);
g_free(marker); g_free(work); g_free(fake);
}
static void test_timeout_cancel_signal_and_absent(void)
{
char *fake = fake_path(); char *work = g_dir_make_tmp("labfy-local-run-XXXXXX", NULL);
LocalToolRunnerProfile limits = profile(); LocalToolRunnerResult *result = NULL;
const char *sleeping[] = {fake, "--sleep", "--ignore-term", NULL};
limits.wall_timeout_ms = 100U;
g_assert_true(local_tool_runner_run(fake, sleeping, work, &limits,
NULL, &result, NULL));
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_TIMEOUT);
g_assert_cmpint(result->elapsed_us, <, 1000000); local_tool_runner_result_free(result);
GCancellable *cancel = g_cancellable_new(); limits.wall_timeout_ms = 1000U;
GThread *thread = g_thread_new("cancel-local", cancel_later, cancel);
result = NULL; g_assert_true(local_tool_runner_run(fake, sleeping, work,
&limits, cancel, &result, NULL)); g_thread_join(thread);
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_CANCELLED);
local_tool_runner_result_free(result); g_object_unref(cancel);
const char *signaled[] = {fake, "--signal", NULL}; result = NULL;
g_assert_true(local_tool_runner_run(fake, signaled, work, &limits,
NULL, &result, NULL));
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_SIGNALED);
local_tool_runner_result_free(result);
const char *missing[] = {"/nonexistent/labfy-tool", NULL}; result = NULL;
g_assert_true(local_tool_runner_run(missing[0], missing, work, &limits,
NULL, &result, NULL));
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_NOT_FOUND);
local_tool_runner_result_free(result); g_rmdir(work); g_free(work); g_free(fake);
}
static void test_descendant_pipe_and_independent_process(void)
{
char *fake = fake_path(); char *work = g_dir_make_tmp("labfy-local-run-XXXXXX", NULL);
pid_t witness = fork();
g_assert_cmpint(witness, >=, 0);
if (witness == 0) { sleep(2); _exit(0); }
const char *argv[] = {fake, "--descendant-pipe", NULL};
LocalToolRunnerProfile limits = profile(); limits.wall_timeout_ms = 150U;
LocalToolRunnerResult *result = NULL;
g_assert_true(local_tool_runner_run(fake, argv, work, &limits,
NULL, &result, NULL));
g_assert_cmpint(result->state, ==, LOCAL_TOOL_RUNNER_TIMEOUT);
g_assert_cmpint(kill(witness, 0), ==, 0);
kill(witness, SIGTERM); waitpid(witness, NULL, 0);
local_tool_runner_result_free(result); g_rmdir(work); g_free(work); g_free(fake);
}
static void test_inherited_fd_and_child_pipe_flags(void)
{
char *fake = fake_path();
char *work = g_dir_make_tmp("labfy-local-fd-XXXXXX", NULL);
char *marker = g_build_filename(work, "SPECIMEN-marker.txt", NULL);
g_assert_true(g_file_set_contents(marker, "M", 1, NULL));
int inherited = open(marker, O_RDONLY);
g_assert_cmpint(inherited, >=, 0);
int flags = fcntl(inherited, F_GETFD);
g_assert_cmpint(fcntl(inherited, F_SETFD, flags & ~FD_CLOEXEC), ==, 0);
char fd_text[32]; g_snprintf(fd_text, sizeof(fd_text), "%d", inherited);
const char *argv[] = {fake, "--inspect-runner", "--fd", fd_text, NULL};
LocalToolRunnerProfile limits = profile();
LocalToolRunnerResult *result = NULL;
g_assert_true(local_tool_runner_run(fake, argv, work, &limits,
NULL, &result, NULL));
gsize size = 0U; const char *data = g_bytes_get_data(result->stdout_bytes, &size);
char *text = g_strndup(data, size);
g_assert_cmpstr(text, ==,
"inherited=0 stdout_nonblocking=0 stderr_nonblocking=0\n");
g_free(text); local_tool_runner_result_free(result); close(inherited);
g_remove(marker); g_rmdir(work); g_free(marker); g_free(work); g_free(fake);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/local-runner/binary-nonzero", test_binary_and_nonzero);
g_test_add_func("/local-runner/output-limit", test_output_limit_and_concurrent);
g_test_add_func("/local-runner/states", test_timeout_cancel_signal_and_absent);
g_test_add_func("/local-runner/descendant-scope", test_descendant_pipe_and_independent_process);
g_test_add_func("/local-runner/fd-and-pipe-flags",
test_inherited_fd_and_child_pipe_flags);
g_test_add_func("/local-runner/process-signal-cancel",
test_process_signal_cancellation_after_start);
return g_test_run();
}