Files
Hermit/tests/test_ctl_frontier.c
T
WHCandCursor 1f1fcba7ad Initial CST-Agent: portable Linux/Termux/Windows coding agent.
Vendored sashidown, OpenAI-compatible env (OPENAI_*), and bootstrap scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 22:42:32 +08:00

157 lines
4.1 KiB
C

#include "ctl.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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;
}