#include "ctl.h" #include #include #include typedef struct { int n_open; int n_arg; int n_ready; int n_text; int n_error; char last_name[CTL_MAX_NAME]; char last_id[CTL_MAX_ID]; char last_path[512]; size_t text_bytes; } sink_t; static void on_ev(const ctl_event_t *ev, void *ud) { sink_t *s = ud; switch (ev->type) { case CTL_EV_CALL_OPEN: s->n_open++; snprintf(s->last_id, sizeof(s->last_id), "%s", ev->call->id); snprintf(s->last_name, sizeof(s->last_name), "%s", ev->call->name); break; case CTL_EV_ARG: s->n_arg++; if (ev->call->nargs > 0) { const ctl_arg_t *a = &ev->call->args[ev->call->nargs - 1]; if (strcmp(a->key, "path") == 0 && a->value) snprintf(s->last_path, sizeof(s->last_path), "%s", a->value); } break; case CTL_EV_CALL_READY: s->n_ready++; break; case CTL_EV_TEXT: s->n_text++; s->text_bytes += ev->text_len; break; case CTL_EV_ERROR: s->n_error++; break; default: break; } } static int expect(int cond, const char *msg) { if (!cond) { fprintf(stderr, "FAIL: %s\n", msg); return 0; } return 1; } static int test_chunked_confirm(void) { sink_t s = {0}; ctl_parser_t *p = ctl_parser_create(on_ev, &s); const char *msg = "I'll read the file.\n" "call t1 read_file\n" "arg path 10\n" "README.mdx" "end\n"; /* feed 1 byte at a time */ for (size_t i = 0; msg[i]; i++) { if (ctl_parser_feed(p, &msg[i], 1) != CTL_OK) { fprintf(stderr, "feed failed at %zu\n", i); ctl_parser_destroy(p); return 0; } } int ok = 1; ok &= expect(ctl_parser_finish(p) == CTL_OK, "finish ok"); ok &= expect(s.n_ready == 1, "one ready"); ok &= expect(s.n_open == 1, "one open"); ok &= expect(s.n_arg == 1, "one arg"); ok &= expect(strcmp(s.last_name, "read_file") == 0, "name"); ok &= expect(strcmp(s.last_path, "README.mdx") == 0, "path"); ok &= expect(s.n_error == 0, "no error"); ok &= expect(ctl_parser_pending(p) == 0, "pending 0"); /* pending peak should be bounded by line/arg */ ok &= expect(ctl_parser_pending_peak(p) <= CTL_MAX_LINE, "peak bounded"); ctl_parser_destroy(p); return ok; } static int test_truncate_no_execute(void) { sink_t s = {0}; ctl_parser_t *p = ctl_parser_create(on_ev, &s); const char *msg = "call t1 read_file\n" "arg path 20\n" "partial"; /* incomplete: need 20 bytes, then end */ ctl_parser_feed(p, msg, strlen(msg)); int ok = 1; ok &= expect(s.n_ready == 0, "no ready before EOF"); ok &= expect(s.n_open == 1, "open confirmed"); ok &= expect(ctl_parser_finish(p) == CTL_ERR_CONTRACT, "finish error"); ok &= expect(s.n_error >= 1, "error emitted"); ok &= expect(s.n_ready == 0, "still no ready"); ctl_parser_destroy(p); return ok; } static int test_monotonic_no_retract(void) { /* Once CALL_READY fires, further feed of garbage should error, not retract */ sink_t s = {0}; ctl_parser_t *p = ctl_parser_create(on_ev, &s); const char *okmsg = "call a1 run_shell\n" "arg cmd 5\n" "ls -l" "end\n"; ctl_parser_feed(p, okmsg, strlen(okmsg)); int ready = s.n_ready; int ok = expect(ready == 1, "ready once"); /* text after is fine */ ctl_parser_feed(p, "done.\n", 6); ok &= expect(s.n_ready == 1, "ready unchanged"); ctl_parser_finish(p); ctl_parser_destroy(p); return ok; } static int test_text_before_call(void) { sink_t s = {0}; ctl_parser_t *p = ctl_parser_create(on_ev, &s); const char *msg = "Hello\nWorld\n"; ctl_parser_feed(p, msg, strlen(msg)); ctl_parser_finish(p); int ok = expect(s.n_text == 2, "two text lines"); ok &= expect(s.n_ready == 0, "no calls"); ctl_parser_destroy(p); return ok; } int main(void) { int nfail = 0; if (!test_chunked_confirm()) nfail++; if (!test_truncate_no_execute()) nfail++; if (!test_monotonic_no_retract()) nfail++; if (!test_text_before_call()) nfail++; if (nfail) { fprintf(stderr, "%d test(s) failed\n", nfail); return 1; } printf("test_ctl_frontier: OK\n"); return 0; }