/* Built-in unified diff: GNU `diff -u` layout on unambiguous cases, and a * randomized round trip through our own patcher. */ #define _POSIX_C_SOURCE 200809L #include "cst_agent.h" #include "cst_tools.h" #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 void put(const char *p, const char *s) { FILE *fp = fopen(p, "wb"); if (fp) { fputs(s, fp); fclose(fp); } } /* GNU diff output, or NULL when diff is not installed. */ static char *gnu_diff(const char *a, const char *b) { char pa[] = "/tmp/cstdiffaXXXXXX", pb[] = "/tmp/cstdiffbXXXXXX"; int fa = mkstemp(pa), fb = mkstemp(pb); if (fa < 0 || fb < 0) return NULL; close(fa); close(fb); put(pa, a); put(pb, b); char cmd[256]; snprintf(cmd, sizeof(cmd), "LC_ALL=C diff -u --label a/f --label b/f %s %s 2>/dev/null", pa, pb); FILE *pp = popen(cmd, "r"); char *out = NULL; if (pp) { size_t cap = 4096, n = 0; out = malloc(cap); int ch; while (out && (ch = fgetc(pp)) != EOF) { if (n + 1 >= cap) { cap *= 2; out = realloc(out, cap); } if (out) out[n++] = (char)ch; } int st = pclose(pp); if (out) out[n] = 0; if (st == -1 || (WEXITSTATUS(st) != 0 && WEXITSTATUS(st) != 1)) { free(out); out = NULL; } } unlink(pa); unlink(pb); return out; } static int have_gnu_diff(void) { char *o = gnu_diff("x\n", "y\n"); int ok = o && strstr(o, "@@ -1 +1 @@"); free(o); return ok; } static char *mine(const char *a, const char *b) { return cst_diff_unified("f", a, strlen(a), b, strlen(b)); } static void same_as_gnu(const char *name, const char *a, const char *b, int gnu) { char *m = mine(a, b); EXPECT(m != NULL, name); if (gnu && m) { char *g = gnu_diff(a, b); if (g && strcmp(g, m) != 0) { fprintf(stderr, "FAIL %s: differs from diff -u\n--- ours\n%s--- gnu\n%s", name, m, g); nfail++; } free(g); } free(m); } /* Apply our diff with our patcher; must reproduce b. */ static int round_trip(const char *a, const char *b) { char *d = mine(a, b); if (!d) return 0; int ok; if (!d[0]) { ok = strcmp(a, b) == 0; } else { const char *h = strstr(d, "\n+++ "); h = h ? strchr(h + 1, '\n') : NULL; char *out = NULL; size_t out_len = 0; int nh = 0; char err[256]; int rc = h ? cst_patch_apply_buffer(a, strlen(a), h + 1, strlen(h + 1), &out, &out_len, &nh, err, sizeof(err)) : -1; ok = rc == 0 && out && out_len == strlen(b) && memcmp(out, b, out_len) == 0; if (!ok) fprintf(stderr, "round trip failed (%s)\n--- a\n%s--- b\n%s--- diff\n%s", rc ? err : "content", a, b, d); free(out); } free(d); return ok; } static char *random_text(unsigned *seed, int lines) { static const char *const words[] = {"alpha", "beta", "gamma", "delta", "{", "}", "", "return 0;"}; size_t cap = (size_t)lines * 16 + 2, n = 0; char *s = malloc(cap); for (int i = 0; i < lines; i++) { const char *w = words[rand_r(seed) % 8]; n += (size_t)snprintf(s + n, cap - n, "%s%d\n", w, rand_r(seed) % 3); } s[n] = 0; if (n && rand_r(seed) % 5 == 0) s[n - 1] = 0; /* no newline at end of file */ return s; } static char *mutate(unsigned *seed, const char *a) { size_t n = strlen(a), cap = n * 2 + 256, o = 0; char *b = malloc(cap); const char *p = a; while (*p) { const char *e = strchr(p, '\n'); size_t len = e ? (size_t)(e - p) + 1 : strlen(p); int r = rand_r(seed) % 10; if (r == 0) { /* drop the line */ } else if (r == 1) { o += (size_t)snprintf(b + o, cap - o, "inserted%d\n", rand_r(seed) % 9); memcpy(b + o, p, len); o += len; } else if (r == 2) { o += (size_t)snprintf(b + o, cap - o, "changed%d\n", rand_r(seed) % 9); } else { memcpy(b + o, p, len); o += len; } p += len; } b[o] = 0; return b; } int main(void) { char *m = mine("same\n", "same\n"); EXPECT(m && m[0] == 0, "identical -> empty"); free(m); int gnu = have_gnu_diff(); if (!gnu) fprintf(stderr, "note: GNU diff not found; layout checks use round trip " "only\n"); same_as_gnu("modify", "a\nb\nc\nd\ne\n", "a\nb\nC\nd\ne\n", gnu); same_as_gnu("insert head", "a\nb\n", "z\na\nb\n", gnu); same_as_gnu("append", "a\nb\n", "a\nb\nc\n", gnu); same_as_gnu("delete", "a\nb\nc\n", "a\nc\n", gnu); same_as_gnu("new file", "", "x\ny\n", gnu); same_as_gnu("emptied", "x\ny\n", "", gnu); same_as_gnu("no eol added", "a\nb", "a\nb\n", gnu); same_as_gnu("no eol kept", "a\nb", "a\nB", gnu); same_as_gnu("two hunks", "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n", "1\nTWO\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\nFOURTEEN\n15\n", gnu); same_as_gnu("merged hunks", "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n", "1\nX\n3\n4\n5\n6\n7\nY\n9\n10\n", gnu); m = mine("a\n", "b\n"); EXPECT(m && strncmp(m, "--- a/f\n+++ b/f\n@@ -1 +1 @@\n-a\n+b\n", 34) == 0, "header layout"); free(m); unsigned seed = 12345; int ok = 0, total = 400; for (int i = 0; i < total; i++) { char *a = random_text(&seed, 1 + (int)(rand_r(&seed) % 60)); char *b = mutate(&seed, a); ok += round_trip(a, b); free(a); free(b); } EXPECT(ok == total, "random round trips"); /* very different large inputs hit the fallback and still round trip */ { size_t n = 6000; char *a = malloc(n * 8), *b = malloc(n * 8); size_t oa = 0, ob = 0; for (size_t i = 0; i < n; i++) { oa += (size_t)sprintf(a + oa, "a%zu\n", i); ob += (size_t)sprintf(b + ob, "b%zu\n", i); } EXPECT(round_trip(a, b), "fallback round trip"); free(a); free(b); } if (nfail) { fprintf(stderr, "test_diff: %d failure(s)\n", nfail); return 1; } printf("test_diff: OK (%d random round trips)\n", total); return 0; }