/* * test_grammar_probe_g.c — Markup / data / config grammar node-creation + edge probe. * * SCOPE * ───── * Probes 27 grammars that have little or no code surface: * html, xml, css, scss, markdown, rst, json, json5, yaml, toml, ini, * csv, sql, soql, sosl, dotenv, gitignore, gitattributes, properties, * requirements, diff, po, regex, sshconfig, bibtex, form, linkerscript * * STRUCTURAL-NODE-BEARING (produce nodes beyond Module): * xml — Class:2 per fixture (element nodes) * toml — Class:1 + Variable:1 (table + key) * ini — Class:1 + Variable:1 (section + key) * json — Variable:2 (top-level keys) * yaml — Variable:2 (top-level keys) * scss — Variable:1 (one def extracted) * sql — Variable:1 (table reference node) * markdown — Section:1 (heading node) * form — Variable:1 (one variable def) * * PURE-DATA Module-only (only a Module node is created): * html, css, rst, json5, soql, sosl, dotenv, gitignore, gitattributes, * properties, requirements, diff, po, regex, sshconfig, bibtex, linkerscript, csv * * COLOUR LEGEND * ───────────── * GREEN = guard: pipeline already produces the result; failure = regression. * RED = bug reproduction: pipeline does NOT yet produce the expected * node/edge; the test FAILS until the bug is fixed. Brief inline * comment documents the root-cause class. * * CALLS/inheritance dimensions are not applicable to these data/markup grammars. * Do NOT register this suite in test_main.c — a sibling agent owns that file. * * SUITE(grammar_probe_g) */ #include "../src/foundation/compat.h" #include "test_framework.h" #include "test_helpers.h" #include "cbm.h" #include #include #include #include #include #include #include #include #include /* ══════════════════════════════════════════════════════════════════ * Harness — mirrors test_grammar_probe_a.c exactly. * Prefix "gpg_" to avoid symbol collisions with sibling probe files. * ══════════════════════════════════════════════════════════════════ */ typedef struct { char tmpdir[256]; char dbpath[512]; char *project; cbm_mcp_server_t *srv; } GpgProj; typedef struct { const char *name; /* relative filename, may include '/' for subdirs */ const char *content; } GpgFile; static void gpg_to_fwd_slashes(char *p) { for (; *p; p++) { if (*p == '\\') *p = '/'; } } static cbm_store_t *gpg_open_indexed(GpgProj *lp) { lp->project = cbm_project_name_from_path(lp->tmpdir); if (!lp->project) return NULL; const char *home = getenv("HOME"); if (!home) home = "/tmp"; char cache_dir[512]; snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home); cbm_mkdir(cache_dir); snprintf(lp->dbpath, sizeof(lp->dbpath), "%s/%s.db", cache_dir, lp->project); unlink(lp->dbpath); lp->srv = cbm_mcp_server_new(NULL); if (!lp->srv) return NULL; char args[700]; snprintf(args, sizeof(args), "{\"repo_path\":\"%s\"}", lp->tmpdir); char *resp = cbm_mcp_handle_tool(lp->srv, "index_repository", args); if (resp) free(resp); return cbm_store_open_path(lp->dbpath); } static cbm_store_t *gpg_index_files(GpgProj *lp, const GpgFile *files, int nfiles) { memset(lp, 0, sizeof(*lp)); snprintf(lp->tmpdir, sizeof(lp->tmpdir), "/tmp/cbm_gpg_XXXXXX"); if (!cbm_mkdtemp(lp->tmpdir)) return NULL; gpg_to_fwd_slashes(lp->tmpdir); for (int i = 0; i < nfiles; i++) { char path[700]; snprintf(path, sizeof(path), "%s/%s", lp->tmpdir, files[i].name); char *slash = strrchr(path, '/'); if (slash && slash > path + strlen(lp->tmpdir)) { *slash = '\0'; cbm_mkdir_p(path, 0755); *slash = '/'; } FILE *f = fopen(path, "wb"); if (!f) return NULL; fputs(files[i].content, f); fclose(f); } return gpg_open_indexed(lp); } static void gpg_cleanup(GpgProj *lp, cbm_store_t *store) { if (store) cbm_store_close(store); if (lp->srv) { cbm_mcp_server_free(lp->srv); lp->srv = NULL; } free(lp->project); lp->project = NULL; th_rmtree(lp->tmpdir); unlink(lp->dbpath); char wal[600], shm[600]; snprintf(wal, sizeof(wal), "%s-wal", lp->dbpath); unlink(wal); snprintf(shm, sizeof(shm), "%s-shm", lp->dbpath); unlink(shm); } /* ── Node-count helpers ─────────────────────────────────────────── */ static int gpg_count_label(cbm_store_t *store, const char *project, const char *label) { cbm_node_t *nodes = NULL; int count = 0; if (cbm_store_find_nodes_by_label(store, project, label, &nodes, &count) != CBM_STORE_OK) return -1; cbm_store_free_nodes(nodes, count); return count; } typedef struct { int ok; int total_nodes; int modules; int classes; int variables; int sections; int imports; /* IMPORTS edges */ int depends; /* DEPENDS_ON edges */ } GpgMetrics; static GpgMetrics gpg_metrics_files(const GpgFile *files, int nfiles) { GpgProj lp; cbm_store_t *store = gpg_index_files(&lp, files, nfiles); GpgMetrics m = {0}; if (store) { m.ok = 1; m.total_nodes = cbm_store_count_nodes(store, lp.project); m.modules = gpg_count_label(store, lp.project, "Module"); m.classes = gpg_count_label(store, lp.project, "Class"); m.variables = gpg_count_label(store, lp.project, "Variable"); m.sections = gpg_count_label(store, lp.project, "Section"); m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS"); m.depends = cbm_store_count_edges_by_type(store, lp.project, "DEPENDS_ON"); } gpg_cleanup(&lp, store); return m; } static GpgMetrics gpg_metrics(const char *filename, const char *content) { GpgFile f = {filename, content}; return gpg_metrics_files(&f, 1); } /* ══════════════════════════════════════════════════════════════════ * GROUP 1 — HTML (.html) * * HTML golden histogram: Module:1 (pure-data — no element nodes extracted). * \n" "\n"}}; GpgMetrics m = gpg_metrics_files(files, 2); ASSERT_TRUE(m.ok); /* RED: HTML