#define _POSIX_C_SOURCE 200809L #include "cst_agent.h" #include #include #include #include #include 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 *apply(const char *orig, const char *hunks, int *rc, int *nh, char *err, size_t esz) { char *out = NULL; size_t out_len = 0; *rc = cst_patch_apply_buffer(orig, strlen(orig), hunks, strlen(hunks), &out, &out_len, nh, err, esz); return out; } static void test_buffer_basic(void) { char err[256]; int rc, nh; const char *orig = "a\nb\nc\nd\ne\n"; char *out = apply(orig, "@@ -2,3 +2,3 @@\n b\n-c\n+C\n d\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && nh == 1, "single hunk applies"); EXPECT(out && strcmp(out, "a\nb\nC\nd\ne\n") == 0, "single hunk result"); free(out); /* two hunks, the second located relative to the shift of the first */ out = apply(orig, "@@ -1,2 +1,3 @@\n a\n+a2\n b\n@@ -4,2 +5,2 @@\n d\n-e\n+E\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && nh == 2, "two hunks apply"); EXPECT(out && strcmp(out, "a\na2\nb\nc\nd\nE\n") == 0, "two hunk result"); free(out); /* wrong line numbers: found by searching near the hint */ out = apply(orig, "@@ -40,1 +40,1 @@\n-d\n+D\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0, "hint off: searched"); EXPECT(out && strcmp(out, "a\nb\nc\nD\ne\n") == 0, "search result"); free(out); /* bare @@ without numbers, unique context */ out = apply(orig, "@@\n c\n-d\n+dd\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && out && strcmp(out, "a\nb\nc\ndd\ne\n") == 0, "bare @@"); free(out); } static void test_buffer_edge(void) { char err[256]; int rc, nh; /* ambiguous context without a line hint is refused */ char *out = apply("x\ny\nx\ny\n", "@@\n-x\n+z\n", &rc, &nh, err, sizeof(err)); EXPECT(rc != 0, "ambiguous refused"); EXPECT(strstr(err, "ambiguous") != NULL, "ambiguous error text"); free(out); /* the same hunk with a hint picks the hinted occurrence */ out = apply("x\ny\nx\ny\n", "@@ -3,1 +3,1 @@\n-x\n+z\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && out && strcmp(out, "x\ny\nz\ny\n") == 0, "hinted occurrence"); free(out); /* missing context fails and changes nothing (all-or-nothing) */ out = apply("a\nb\n", "@@ -1,1 +1,1 @@\n-a\n+A\n@@ -9,1 +9,1 @@\n-zzz\n+q\n", &rc, &nh, err, sizeof(err)); EXPECT(rc != 0 && out == NULL, "second hunk fails -> nothing"); free(out); /* trailing whitespace drift is tolerated by the loose pass */ out = apply("int x; \nint y;\n", "@@ -1,2 +1,2 @@\n int x;\n-int y;\n+int z;\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && out && strstr(out, "int z;"), "rtrim-loose match"); free(out); /* no final newline preserved / added */ out = apply("a\nb", "@@ -2 +2 @@\n-b\n\\ No newline at end of file\n+B\n" "\\ No newline at end of file\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && out && strcmp(out, "a\nB") == 0, "no final newline kept"); free(out); /* a blank line inside a hunk is empty context (models drop the space) */ out = apply("a\n\nb\n", "@@ -1,3 +1,3 @@\n a\n\n-b\n+B\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && out && strcmp(out, "a\n\nB\n") == 0, "blank context line"); free(out); /* tabs survive */ out = apply("all:\n\tgcc a.c\n", "@@ -2 +2 @@\n-\tgcc a.c\n+\tgcc -O2 a.c\n", &rc, &nh, err, sizeof(err)); EXPECT(rc == 0 && out && strcmp(out, "all:\n\tgcc -O2 a.c\n") == 0, "tabs"); free(out); } static char g_ws[256]; static void put(const char *rel, const char *content) { char p[512]; snprintf(p, sizeof(p), "%s/%s", g_ws, rel); FILE *fp = fopen(p, "wb"); fputs(content, fp); fclose(fp); } static char *get(const char *rel) { char p[512]; snprintf(p, sizeof(p), "%s/%s", g_ws, rel); FILE *fp = fopen(p, "rb"); if (!fp) return NULL; static char buf[8192]; size_t n = fread(buf, 1, sizeof(buf) - 1, fp); buf[n] = 0; fclose(fp); return buf; } static int run_patch(const char *patch, cst_tool_result_t *r) { ctl_tool_call_t c; memset(&c, 0, sizeof(c)); snprintf(c.id, sizeof(c.id), "t"); snprintf(c.name, sizeof(c.name), "apply_patch"); snprintf(c.args[0].key, sizeof(c.args[0].key), "patch"); c.args[0].value = (char *)patch; c.args[0].value_len = strlen(patch); c.nargs = 1; memset(r, 0, sizeof(*r)); cst_tool_dispatch(g_ws, &c, r); return r->exit_code; } static void test_tool_multi_file(void) { put("a.c", "int a = 1;\n"); put("b.c", "int b = 2;\n"); put("gone.c", "x\n"); cst_tool_result_t r; int ec = run_patch("--- a/a.c\n+++ b/a.c\n@@ -1 +1 @@\n-int a = 1;\n+int a = 10;\n" "--- a/b.c\n+++ b/b.c\n@@ -1 +1 @@\n-int b = 2;\n+int b = 20;\n" "--- /dev/null\n+++ b/sub/dir/new.c\n@@ -0,0 +1,2 @@\n+one\n+two\n" "--- a/gone.c\n+++ /dev/null\n@@ -1 +0,0 @@\n-x\n", &r); EXPECT(ec == 0, "multi-file patch ok"); if (ec != 0) fprintf(stderr, "%.*s\n", (int)r.out_len, r.out ? r.out : ""); cst_tool_result_free(&r); char *s = get("a.c"); EXPECT(s && strcmp(s, "int a = 10;\n") == 0, "a.c updated"); s = get("b.c"); EXPECT(s && strcmp(s, "int b = 20;\n") == 0, "b.c updated"); s = get("sub/dir/new.c"); EXPECT(s && strcmp(s, "one\ntwo\n") == 0, "new file with parent dirs"); EXPECT(get("gone.c") == NULL, "deleted file"); /* second file fails -> first file untouched */ ec = run_patch("--- a/a.c\n+++ b/a.c\n@@ -1 +1 @@\n-int a = 10;\n+int a = 99;\n" "--- a/b.c\n+++ b/b.c\n@@ -1 +1 @@\n-no such line\n+q\n", &r); EXPECT(ec != 0, "failing multi-file patch reports error"); cst_tool_result_free(&r); s = get("a.c"); EXPECT(s && strcmp(s, "int a = 10;\n") == 0, "all-or-nothing across files"); /* escaping the workspace is refused */ ec = run_patch("--- /dev/null\n+++ b/../escape.c\n@@ -0,0 +1 @@\n+x\n", &r); EXPECT(ec != 0, "path escape refused"); cst_tool_result_free(&r); } int main(void) { test_buffer_basic(); test_buffer_edge(); snprintf(g_ws, sizeof(g_ws), "/tmp/cst-patch-test-%d", (int)getpid()); mkdir(g_ws, 0755); test_tool_multi_file(); char cmd[300]; snprintf(cmd, sizeof(cmd), "rm -rf %s /tmp/escape.c", g_ws); if (system(cmd) != 0) fprintf(stderr, "warning: cleanup failed\n"); if (nfail) { fprintf(stderr, "test_apply_patch: %d failed\n", nfail); return 1; } printf("test_apply_patch: OK\n"); return 0; }