/* * test_extends_stress.c -- multi-file %extends graph stress test. * * Builds large %extends graphs at runtime in a tmpdir and runs the * lime CLI against the leaf file. Validates that the graph * traversal and override-merge logic scale to 100+ files. * * Original deferred from v0.6.x roadmap as * "Multi-file %extends graphs across mailing-list-archive scale -- * works at the 1-10 file scale today; haven't stress-tested 100+." * * Sub-tests: * * 1. linear chain (N=128). Builds chain_000.lime through * chain_127.lime where each file %extends the prior one and * adds one rule. Compiles the leaf and asserts the resulting * parser includes all 128 rules. * * 2. fan-in diamond (N=64). Builds 64 leaves all %extending a * common base, plus a single tip that %extends every leaf. * Diamond-base appears once in the merged grammar (no rule * duplication). * * 3. wide-and-deep (8 deep x 8 wide). Each level has 8 siblings * that all extend the prior level's tip. 64 files; tests both * depth-first and breadth-first traversal in the cycle * detector. * * Skipped at runtime when the lime CLI is unreachable (LIME_BIN * unset and `lime` not on PATH). */ #include #include #include #include #include #include #include #include #include #include static int test_count = 0; static int pass_count = 0; static int skip_count = 0; #define TEST(name) do { \ printf("[TEST %d] %s\n", ++test_count, name); fflush(stdout); \ } while (0) #define ASSERT(cond, msg) do { \ if (!(cond)) { fprintf(stderr, " FAIL: %s\n", msg); return; } \ } while (0) #define PASS() do { printf(" PASS\n"); pass_count++; } while (0) #define SKIP(reason) do { printf(" SKIP: %s\n", reason); skip_count++; } while (0) static const char *g_lime_bin = NULL; static int subprocess_available(void) { g_lime_bin = getenv("LIME_BIN"); if (g_lime_bin && access(g_lime_bin, X_OK) == 0) return 1; g_lime_bin = "lime"; return system("command -v lime > /dev/null 2>&1") == 0; } static char *make_tmpdir(void) { static char tmp[256]; const char *base = getenv("TMPDIR"); if (!base || !*base) base = "/tmp"; snprintf(tmp, sizeof(tmp), "%s/lime_extends_stress_XXXXXX", base); return mkdtemp(tmp); } static void rm_rf_dir(const char *dir) { char cmd[512]; snprintf(cmd, sizeof(cmd), "rm -rf %s", dir); /* Test cleanup; rm -rf on our own mkdtemp output is the only * place in this codebase that uses it. */ int rc = system(cmd); (void)rc; } static int write_file(const char *path, const char *contents) { FILE *fp = fopen(path, "wb"); if (!fp) return -1; size_t n = strlen(contents); int ok = fwrite(contents, 1, n, fp) == n; fclose(fp); return ok ? 0 : -1; } /* Discover the lime parser template (limpar.c). Searched in * project layout: $LIME_TEMPLATE -> $LIME_SRC_INCLUDE/../limpar.c * -> ../limpar.c relative to the test binary's notion of project * root (LIME_PROJECT_ROOT env, set by meson). */ static const char *g_template_path = NULL; static void resolve_template(void) { static char buf[1024]; const char *t = getenv("LIME_TEMPLATE"); if (t && access(t, R_OK) == 0) { g_template_path = t; return; } const char *root = getenv("LIME_PROJECT_ROOT"); if (root) { snprintf(buf, sizeof(buf), "%s/limpar.c", root); if (access(buf, R_OK) == 0) { g_template_path = buf; return; } } /* Fall back to "../limpar.c" relative to lime binary path. */ if (g_lime_bin && g_lime_bin[0] == '/') { const char *slash = strrchr(g_lime_bin, '/'); if (slash) { size_t len = (size_t)(slash - g_lime_bin); snprintf(buf, sizeof(buf), "%.*s/../limpar.c", (int)len, g_lime_bin); if (access(buf, R_OK) == 0) { g_template_path = buf; return; } } } g_template_path = NULL; } /* Run `lime -T