#define _POSIX_C_SOURCE 200809L #include "cst_agent.h" #include "os_compat.h" #include #include #include #include #include /* run_shell must never hang the agent: timeout kills the whole process * group, stdin is empty, huge output is capped. */ static int nfail; #define EXPECT(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, msg); \ nfail++; \ } \ } while (0) static char g_ws[256]; static int sh(const char *cmd, cst_tool_result_t *r, double *ms) { ctl_tool_call_t c; memset(&c, 0, sizeof(c)); snprintf(c.id, sizeof(c.id), "s"); snprintf(c.name, sizeof(c.name), "run_shell"); snprintf(c.args[0].key, sizeof(c.args[0].key), "cmd"); c.args[0].value = (char *)cmd; c.args[0].value_len = strlen(cmd); c.nargs = 1; memset(r, 0, sizeof(*r)); double t0 = os_now_ms(); cst_tool_dispatch(g_ws, &c, r); *ms = os_now_ms() - t0; return r->exit_code; } int main(void) { snprintf(g_ws, sizeof(g_ws), "/tmp/cst-shell-test-%d", (int)getpid()); mkdir(g_ws, 0755); cst_tool_result_t r; double ms; EXPECT(sh("echo hi; exit 3", &r, &ms) == 3, "exit code passed through"); EXPECT(r.out && strstr(r.out, "hi"), "stdout captured"); cst_tool_result_free(&r); /* stdin is /dev/null: a command reading stdin returns at once */ sh("cat; echo done", &r, &ms); EXPECT(r.out && strstr(r.out, "done") && ms < 5000, "stdin is empty"); cst_tool_result_free(&r); /* timeout kills the process group, including background children that * keep the output pipe open */ setenv("CST_SHELL_TIMEOUT", "1", 1); os_set_run_timeout(1); int ec = sh("sleep 30 & sleep 30; echo never", &r, &ms); EXPECT(ec == 124, "timeout exit code 124"); EXPECT(ms < 8000, "timeout returns promptly"); EXPECT(!r.out || !strstr(r.out, "never"), "command was stopped"); cst_tool_result_free(&r); os_set_run_timeout(0); unsetenv("CST_SHELL_TIMEOUT"); /* the shell exits but leaves a background child holding the pipe */ ec = sh("sleep 30 & echo started", &r, &ms); EXPECT(ec == 0, "shell exit code kept when a background child lingers"); EXPECT(r.out && strstr(r.out, "started"), "foreground output kept"); EXPECT(ms < 5000, "lingering background child does not block"); cst_tool_result_free(&r); /* huge output: capped and marked, the child is not blocked on the pipe */ sh("head -c 5000000 /dev/zero | tr '\\0' x; echo; echo tail-marker", &r, &ms); EXPECT(r.out && r.out_len < 2000000, "output capped"); EXPECT(r.out && strstr(r.out, "truncated"), "truncation noted"); EXPECT(ms < 20000, "large output does not hang"); cst_tool_result_free(&r); char cmd[300]; snprintf(cmd, sizeof(cmd), "rm -rf %s", g_ws); if (system(cmd) != 0) fprintf(stderr, "warning: cleanup failed\n"); if (nfail) { fprintf(stderr, "test_shell: %d failed\n", nfail); return 1; } printf("test_shell: OK\n"); return 0; }