Files
wehub-resource-sync 41cb1c0170
OpenSSF Scorecard / scorecard (push) Failing after 0s
DCO / dco (push) Failing after 0s
CodeQL SAST / analyze (push) Failing after 1s
Deploy Pages / deploy (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:28:05 +08:00

1136 lines
53 KiB
C

/*
* 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 <mcp/mcp.h>
#include <store/store.h>
#include <pipeline/pipeline.h>
#include <foundation/log.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
/* ══════════════════════════════════════════════════════════════════
* 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).
* <script src="..."> and <link href="..."> are the only edges the pipeline
* could model, but no HTML IMPORTS resolver exists in the pipeline today.
* ══════════════════════════════════════════════════════════════════ */
/* HTML: minimal page → at least 1 Module node, no crash. */
TEST(probe_html_minimal_page) {
GpgMetrics m = gpg_metrics("index.html", "<!DOCTYPE html>\n"
"<html><head><title>Test</title></head>\n"
"<body><h1>Hello</h1></body></html>\n");
ASSERT_TRUE(m.ok);
/* GREEN: HTML always produces at least a Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* HTML: file with <script src> and <link rel=stylesheet> → IMPORTS edges.
* RED: no HTML import resolver; script/link attributes are not walked into
* IMPORTS edges by the pipeline. */
TEST(probe_html_script_imports) {
static const GpgFile files[] = {{"app.js", "function init() { console.log('ready'); }\n"},
{"index.html", "<!DOCTYPE html><html><head>\n"
"<link rel=\"stylesheet\" href=\"style.css\">\n"
"<script src=\"app.js\"></script>\n"
"</head><body></body></html>\n"}};
GpgMetrics m = gpg_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: HTML <script src> / <link href> not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no HTML import resolver */
PASS();
}
/* HTML: pure-data — no Class/Variable/Section nodes expected. */
TEST(probe_html_no_structural_nodes) {
GpgMetrics m = gpg_metrics("page.html", "<html><body><p>text</p></body></html>\n");
ASSERT_TRUE(m.ok);
/* GREEN: HTML produces zero Class/Variable/Section nodes (pure-data). */
ASSERT_TRUE(m.classes == 0);
ASSERT_TRUE(m.variables == 0);
ASSERT_TRUE(m.sections == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 2 — XML (.xml)
*
* XML golden histogram: Class:2, Module:1
* The XML extractor produces Class nodes for element definitions
* (e.g., root element + nested element type nodes).
* ══════════════════════════════════════════════════════════════════ */
/* XML: minimal document → Module node + at least 2 Class nodes. */
TEST(probe_xml_element_nodes) {
GpgMetrics m = gpg_metrics("config.xml", "<?xml version=\"1.0\"?>\n"
"<config>\n"
" <server host=\"localhost\" port=\"8080\"/>\n"
" <database name=\"mydb\"/>\n"
"</config>\n");
ASSERT_TRUE(m.ok);
/* GREEN: XML extractor produces Class nodes for elements. */
ASSERT_TRUE(m.classes >= 2);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* XML: more elements → more Class nodes (≥ 2). */
TEST(probe_xml_multiple_elements) {
GpgMetrics m = gpg_metrics("pom.xml", "<project>\n"
" <groupId>com.example</groupId>\n"
" <artifactId>demo</artifactId>\n"
" <version>1.0</version>\n"
"</project>\n");
ASSERT_TRUE(m.ok);
/* GREEN: multiple element types must produce Class nodes. */
ASSERT_TRUE(m.classes >= 2);
PASS();
}
/* XML: no-crash guard for empty document. */
TEST(probe_xml_no_crash) {
GpgMetrics m = gpg_metrics("empty.xml", "<?xml version=\"1.0\"?><root/>\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.total_nodes >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 3 — CSS (.css)
*
* CSS golden histogram: Module:1 (pure-data).
* CSS selectors/rules are not extracted as graph nodes; only a Module
* node is emitted per file.
* ══════════════════════════════════════════════════════════════════ */
/* CSS: rules file → only a Module node. */
TEST(probe_css_module_only) {
GpgMetrics m = gpg_metrics("styles.css", "body {\n"
" margin: 0;\n"
" font-family: sans-serif;\n"
"}\n"
".container { max-width: 1200px; }\n"
"#header { background: #fff; }\n");
ASSERT_TRUE(m.ok);
/* GREEN: CSS produces exactly 1 Module node; no Class/Variable/Section. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.classes == 0);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* CSS: @import in two-file fixture → IMPORTS edge.
* RED: no CSS import resolver in the pipeline. */
TEST(probe_css_at_import) {
static const GpgFile files[] = {{"base.css", "* { box-sizing: border-box; }\n"},
{"main.css", "@import \"base.css\";\n"
"body { margin: 0; }\n"}};
GpgMetrics m = gpg_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: CSS @import not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no CSS import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 4 — SCSS (.scss)
*
* SCSS golden histogram: Module:1, Variable:1
* The extractor pulls at least one Variable node (e.g. a CSS custom property
* or SCSS variable). No selector/rule Class nodes.
* ══════════════════════════════════════════════════════════════════ */
/* SCSS: variables + rules → at least 1 Variable node. */
TEST(probe_scss_variable_node) {
GpgMetrics m = gpg_metrics("theme.scss", "$primary: #3498db;\n"
"$secondary: #2ecc71;\n"
"\n"
".button {\n"
" background: $primary;\n"
" color: white;\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: SCSS extracts at least 1 Variable node (histogram: Variable:1). */
ASSERT_TRUE(m.variables >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* SCSS: @import / @use in two-file fixture → IMPORTS edge.
* RED: no SCSS import resolver in the pipeline. */
TEST(probe_scss_at_use_imports) {
static const GpgFile files[] = {{"_vars.scss", "$brand: #e74c3c;\n"},
{"main.scss", "@use 'vars';\n"
".hero { color: vars.$brand; }\n"}};
GpgMetrics m = gpg_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: SCSS @use / @import not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no SCSS import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 5 — Markdown (.md)
*
* Markdown golden histogram: Module:1, Section:1
* Headings become Section nodes. Links are NOT resolved to IMPORTS edges.
* ══════════════════════════════════════════════════════════════════ */
/* Markdown: document with headings → at least 1 Section node. */
TEST(probe_markdown_section_node) {
GpgMetrics m = gpg_metrics("README.md", "# Project Title\n"
"\n"
"Description text.\n"
"\n"
"## Getting Started\n"
"\n"
"Install with `npm install`.\n");
ASSERT_TRUE(m.ok);
/* GREEN: Markdown headings produce Section nodes. */
ASSERT_TRUE(m.sections >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Markdown: multiple headings → at least 1 Section (histogram baseline: Section:1).
* The histogram fixture has 1 section; additional headings may or may not add more. */
TEST(probe_markdown_multi_heading) {
GpgMetrics m = gpg_metrics("docs.md", "# Title\n"
"## Setup\n"
"### Install\n"
"## Usage\n");
ASSERT_TRUE(m.ok);
/* GREEN: at minimum 1 Section node. */
ASSERT_TRUE(m.sections >= 1);
PASS();
}
/* Markdown: no Class/Variable nodes (pure-data beyond Section). */
TEST(probe_markdown_no_class_variable) {
GpgMetrics m = gpg_metrics("notes.md", "# Notes\n\nSome content.\n");
ASSERT_TRUE(m.ok);
/* GREEN: Markdown does not produce Class or Variable nodes. */
ASSERT_TRUE(m.classes == 0);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 6 — RST (.rst)
*
* RST golden histogram: Module:1 (pure-data).
* reStructuredText headings are NOT extracted as Section nodes (unlike Markdown).
* ══════════════════════════════════════════════════════════════════ */
/* RST: document with sections → Module:1, no Section nodes. */
TEST(probe_rst_module_only) {
GpgMetrics m = gpg_metrics("docs.rst", "Project Title\n"
"=============\n"
"\n"
"Description text.\n"
"\n"
"Installation\n"
"------------\n"
"\n"
"Run ``pip install mypackage``.\n");
ASSERT_TRUE(m.ok);
/* GREEN: RST produces only a Module node (no Section nodes extracted). */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.sections == 0);
PASS();
}
/* RST: no-crash guard. */
TEST(probe_rst_no_crash) {
GpgMetrics m = gpg_metrics("api.rst", ".. automodule:: mypackage.api\n"
" :members:\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.total_nodes >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 7 — JSON (.json)
*
* JSON golden histogram: Module:1, Variable:2
* Top-level keys become Variable nodes (2 in the fixture).
* ══════════════════════════════════════════════════════════════════ */
/* JSON: object with two top-level keys → 2 Variable nodes. */
TEST(probe_json_variable_nodes) {
GpgMetrics m = gpg_metrics("config.json", "{\n"
" \"name\": \"myproject\",\n"
" \"version\": \"1.0.0\"\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: JSON top-level keys become Variable nodes. */
ASSERT_TRUE(m.variables >= 2);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* JSON: three top-level keys → ≥ 2 Variable nodes (floor from histogram). */
TEST(probe_json_more_variables) {
/* Fixture fix: "package.json" is in discover.c IGNORED_JSON_FILES, so
* detect_file_language() returns CBM_LANG_COUNT and the file is never
* indexed (0 nodes). Use a generic .json filename so the JSON grammar
* actually runs (grammar_labels json=Variable:2). */
GpgMetrics m = gpg_metrics("data.json", "{\n"
" \"name\": \"demo\",\n"
" \"version\": \"0.1.0\",\n"
" \"description\": \"A demo project\"\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: at least 2 Variable nodes (histogram baseline). */
ASSERT_TRUE(m.variables >= 2);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 8 — JSON5 (.json5)
*
* JSON5 golden histogram: Module:1 (pure-data).
* Despite being a superset of JSON, the JSON5 extractor produces only Module.
* ══════════════════════════════════════════════════════════════════ */
/* JSON5: file with keys → only Module node. */
TEST(probe_json5_module_only) {
GpgMetrics m = gpg_metrics("config.json5", "{\n"
" // comments allowed\n"
" name: 'myproject',\n"
" version: '1.0',\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: JSON5 produces only a Module node (no Variable nodes unlike JSON). */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 9 — YAML (.yaml)
*
* YAML golden histogram: Module:1, Variable:2
* Top-level keys become Variable nodes.
* ══════════════════════════════════════════════════════════════════ */
/* YAML: document with two top-level keys → 2 Variable nodes. */
TEST(probe_yaml_variable_nodes) {
GpgMetrics m = gpg_metrics("config.yaml", "name: myproject\n"
"version: 1.0.0\n");
ASSERT_TRUE(m.ok);
/* GREEN: YAML top-level keys become Variable nodes. */
ASSERT_TRUE(m.variables >= 2);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* YAML: three top-level keys → still ≥ 2 Variable nodes (baseline). */
TEST(probe_yaml_three_keys) {
GpgMetrics m = gpg_metrics("app.yaml", "host: localhost\n"
"port: 8080\n"
"debug: true\n");
ASSERT_TRUE(m.ok);
/* GREEN: at least 2 Variable nodes. */
ASSERT_TRUE(m.variables >= 2);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 10 — TOML (.toml)
*
* TOML golden histogram: Class:1, Module:1, Variable:1
* Table headers produce Class nodes; top-level keys produce Variable nodes.
* ══════════════════════════════════════════════════════════════════ */
/* TOML: file with table + key → Class:1 + Variable:1. */
TEST(probe_toml_table_and_key) {
GpgMetrics m = gpg_metrics("Cargo.toml", "[package]\n"
"name = \"mylib\"\n"
"version = \"0.1.0\"\n");
ASSERT_TRUE(m.ok);
/* GREEN: TOML table [package] becomes a Class node. */
ASSERT_TRUE(m.classes >= 1);
/* GREEN: key becomes a Variable node. */
ASSERT_TRUE(m.variables >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* TOML: multiple tables → ≥ 1 Class node (may be exactly 1 per fixture). */
TEST(probe_toml_multiple_tables) {
GpgMetrics m = gpg_metrics("pyproject.toml", "[tool.poetry]\n"
"name = \"demo\"\n"
"\n"
"[tool.poetry.dependencies]\n"
"python = \"^3.9\"\n");
ASSERT_TRUE(m.ok);
/* GREEN: at least one Class node for tables. */
ASSERT_TRUE(m.classes >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 11 — INI (.ini)
*
* INI golden histogram: Class:1, Module:1, Variable:1
* Sections produce Class nodes; key-value pairs produce Variable nodes.
* ══════════════════════════════════════════════════════════════════ */
/* INI: file with a section and a key → Class + Variable. */
TEST(probe_ini_section_and_key) {
GpgMetrics m = gpg_metrics("app.ini", "[database]\n"
"host = localhost\n"
"port = 5432\n");
ASSERT_TRUE(m.ok);
/* GREEN: [database] section becomes a Class node. */
ASSERT_TRUE(m.classes >= 1);
/* GREEN: key becomes a Variable node. */
ASSERT_TRUE(m.variables >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* INI: .conf extension also maps to CBM_LANG_INI; must produce same structure. */
TEST(probe_ini_conf_extension) {
GpgMetrics m = gpg_metrics("server.conf", "[server]\n"
"listen = 0.0.0.0\n");
ASSERT_TRUE(m.ok);
/* GREEN: .conf files index as INI and produce Class + Variable nodes. */
ASSERT_TRUE(m.classes >= 1);
ASSERT_TRUE(m.variables >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 12 — CSV (.csv)
*
* CSV golden histogram: Module:1 (pure-data).
* Tabular data — no structural nodes beyond Module.
* ══════════════════════════════════════════════════════════════════ */
/* CSV: file with header + rows → only Module node. */
TEST(probe_csv_module_only) {
GpgMetrics m = gpg_metrics("data.csv", "id,name,score\n"
"1,Alice,95\n"
"2,Bob,87\n"
"3,Carol,92\n");
ASSERT_TRUE(m.ok);
/* GREEN: CSV produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.classes == 0);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 13 — SQL (.sql)
*
* SQL golden histogram: Module:1, Variable:1
* Table references (e.g. CREATE TABLE / SELECT FROM) produce Variable nodes.
* ══════════════════════════════════════════════════════════════════ */
/* SQL: CREATE TABLE + SELECT → at least 1 Variable node. */
TEST(probe_sql_variable_node) {
GpgMetrics m = gpg_metrics("schema.sql", "CREATE TABLE users (\n"
" id INTEGER PRIMARY KEY,\n"
" name TEXT NOT NULL\n"
");\n"
"\n"
"SELECT id, name FROM users WHERE id = 1;\n");
ASSERT_TRUE(m.ok);
/* GREEN: SQL table reference produces at least 1 Variable node. */
ASSERT_TRUE(m.variables >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* SQL: multiple statements → still at least 1 Variable. */
TEST(probe_sql_insert_select) {
GpgMetrics m = gpg_metrics(
"queries.sql", "INSERT INTO orders (user_id, total) VALUES (1, 99.99);\n"
"SELECT o.id, u.name FROM orders o JOIN users u ON o.user_id = u.id;\n");
ASSERT_TRUE(m.ok);
/* GREEN (fixture fix): SQL Variable nodes come ONLY from DDL
* create_table/create_view (lang_specs.c sql_var_types), NOT from
* DML table *references* in INSERT/SELECT. A DML-only file correctly
* yields 0 Variable nodes; the original `>= 1` asserted a non-feature.
* Assert the true contract: no table definitions → 0 Variables. */
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 14 — SOQL (.soql)
*
* SOQL golden histogram: Module:1 (pure-data).
* Salesforce Object Query Language — no structural nodes extracted.
* ══════════════════════════════════════════════════════════════════ */
/* SOQL: SELECT query → only Module node. */
TEST(probe_soql_module_only) {
GpgMetrics m = gpg_metrics("query.soql", "SELECT Id, Name, Email\n"
"FROM Contact\n"
"WHERE AccountId = :accountId\n"
"ORDER BY Name\n");
ASSERT_TRUE(m.ok);
/* GREEN: SOQL produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 15 — SOSL (.sosl)
*
* SOSL golden histogram: Module:1 (pure-data).
* Salesforce Object Search Language — no structural nodes extracted.
* ══════════════════════════════════════════════════════════════════ */
/* SOSL: FIND query → only Module node. */
TEST(probe_sosl_module_only) {
GpgMetrics m = gpg_metrics("search.sosl", "FIND {Acme} IN ALL FIELDS\n"
"RETURNING Account(Id, Name), Contact(Id, Name)\n");
ASSERT_TRUE(m.ok);
/* GREEN: SOSL produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 16 — DotEnv (.env)
*
* DotEnv golden histogram: Module:1 (pure-data).
* Environment variable files are pure-data; keys are NOT extracted as nodes.
* Extension: .env suffix detected by pass_envscan; filename ".env" also works.
* ══════════════════════════════════════════════════════════════════ */
/* DotEnv: .env file → only Module node. */
TEST(probe_dotenv_module_only) {
GpgMetrics m = gpg_metrics(".env", "DATABASE_URL=postgres://localhost/mydb\n"
"SECRET_KEY=supersecret\n"
"DEBUG=true\n");
ASSERT_TRUE(m.ok);
/* REAL BUG (NEW class — file-index routing gap): the DotEnv grammar +
* histogram (grammar_labels dotenv=Module:1) exist and work via DIRECT
* extraction, but ".env" has no FILENAME_TABLE / EXT_TABLE entry in
* language.c, so file-based index_repository's cbm_language_for_filename
* returns CBM_LANG_COUNT and the file is never indexed → 0 Module nodes.
* Routing/registration gap, distinct from extraction classes 2/16. RED. */
ASSERT_TRUE(m.modules == 1); /* REAL BUG — .env not routed by file index */
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* DotEnv: .env.local suffix variant → indexed as DotEnv. */
TEST(probe_dotenv_local_suffix) {
GpgMetrics m = gpg_metrics(".env.local", "API_KEY=local-test-key\n"
"PORT=3001\n");
ASSERT_TRUE(m.ok);
/* REAL BUG (NEW class — file-index routing gap): ".env.local" has no
* FILENAME_TABLE/EXT_TABLE entry, so file-based index_repository never
* routes it to CBM_LANG_DOTENV → 0 Module nodes. Same routing gap as
* probe_dotenv_module_only. RED. */
ASSERT_TRUE(m.modules >= 1); /* REAL BUG — .env.local not routed by file index */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 17 — gitignore (.gitignore)
*
* gitignore golden histogram: Module:1 (pure-data).
* Pattern files — no structural graph nodes.
* Extension: cbm_path_ext(".gitignore") → "gitignore" (strips the leading dot).
* ══════════════════════════════════════════════════════════════════ */
/* gitignore: pattern file → only Module node. */
TEST(probe_gitignore_module_only) {
GpgMetrics m = gpg_metrics(".gitignore", "*.o\n"
"*.a\n"
"build/\n"
"dist/\n"
".DS_Store\n");
ASSERT_TRUE(m.ok);
/* CONTRACT (corrected): unlike .gitattributes, ".gitignore" is intentionally
* NOT indexed as a source file — the cbm_discover walker consumes it as
* ignore-rule input (see test_discover.c discover_with_gitignore /
* discover_gitignore_dir_excluded_issue234 / discover_cbmignore_stacks,
* which assert .gitignore is excluded from the discovered file set). The
* gitignore grammar exists for DIRECT extraction only; via the file walker
* the ignore-control role wins by design, so it yields 0 nodes. Routing it
* to CBM_LANG_GITIGNORE would regress those three established tests. */
ASSERT_TRUE(m.modules == 0); /* .gitignore consumed as ignore-rules, not indexed (by design) */
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 18 — gitattributes (.gitattributes)
*
* gitattributes golden histogram: Module:1 (pure-data).
* ══════════════════════════════════════════════════════════════════ */
/* gitattributes: attribute file → only Module node. */
TEST(probe_gitattributes_module_only) {
GpgMetrics m = gpg_metrics(".gitattributes", "*.c text eol=lf\n"
"*.h text eol=lf\n"
"*.png binary\n"
"*.jpg binary\n");
ASSERT_TRUE(m.ok);
/* REAL BUG (NEW class — file-index routing gap): gitattributes grammar +
* histogram (Module:1) work via DIRECT extraction, but ".gitattributes"
* has no FILENAME_TABLE/EXT_TABLE entry → file-based index_repository
* never routes it → 0 Module nodes. Same routing gap as .gitignore. */
ASSERT_TRUE(m.modules == 1); /* REAL BUG — .gitattributes not routed by file index */
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 19 — Properties (.properties)
*
* Properties golden histogram: Module:1 (pure-data).
* Java-style key=value files — keys NOT extracted as Variable nodes
* (unlike INI which does produce Class/Variable nodes).
* ══════════════════════════════════════════════════════════════════ */
/* Properties: key=value file → only Module node. */
TEST(probe_properties_module_only) {
GpgMetrics m = gpg_metrics("app.properties", "server.host=localhost\n"
"server.port=8080\n"
"log.level=INFO\n");
ASSERT_TRUE(m.ok);
/* GREEN: .properties produces a Module node plus one Variable per `key=value`
* property line (extract_defs.c CBM_LANG_PROPERTIES → push_var_def). The
* fixture has 3 property lines (server.host, server.port, log.level). */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 3);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 20 — Requirements (.txt via FILENAME_TABLE)
*
* Requirements golden histogram: Module:1 (pure-data).
* Python dependency specs — deps NOT extracted as DEPENDS_ON edges by
* the grammar-only pipeline (no requirements resolver).
* Extension: recognized only for "requirements.txt", "requirements-dev.txt",
* "requirements-test.txt" (FILENAME_TABLE exact match).
* ══════════════════════════════════════════════════════════════════ */
/* Requirements: dep file → only Module node. */
TEST(probe_requirements_module_only) {
GpgMetrics m = gpg_metrics("requirements.txt", "flask==2.3.0\n"
"requests>=2.28.0\n"
"sqlalchemy~=2.0\n");
ASSERT_TRUE(m.ok);
/* GREEN: requirements.txt produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
PASS();
}
/* Requirements: DEPENDS_ON edges for package entries.
* RED: grammar-only requirements has no dependency-edge resolver. */
TEST(probe_requirements_depends_on_edge) {
GpgMetrics m = gpg_metrics("requirements.txt", "numpy==1.24.0\n"
"pandas>=1.5.0\n"
"scipy>=1.10.0\n");
ASSERT_TRUE(m.ok);
/* RED: requirements deps not resolved into DEPENDS_ON edges. */
ASSERT_TRUE(m.depends >= 1); /* expected RED — no requirements edge resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 21 — Diff (.diff / .patch)
*
* Diff golden histogram: Module:1 (pure-data).
* Patch files are structural metadata, not code.
* ══════════════════════════════════════════════════════════════════ */
/* Diff: unified diff → only Module node. */
TEST(probe_diff_module_only) {
GpgMetrics m = gpg_metrics("changes.diff", "--- a/main.c\n"
"+++ b/main.c\n"
"@@ -1,5 +1,6 @@\n"
" #include <stdio.h>\n"
"+#include <stdlib.h>\n"
" \n"
" int main(void) {\n"
"- printf(\"hello\\n\");\n"
"+ printf(\"hello world\\n\");\n"
" return 0;\n"
" }\n");
ASSERT_TRUE(m.ok);
/* GREEN: diff produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 22 — PO (.po)
*
* PO golden histogram: Module:1 (pure-data).
* GNU gettext translation catalogs — no structural nodes.
* ══════════════════════════════════════════════════════════════════ */
/* PO: translation file → only Module node. */
TEST(probe_po_module_only) {
GpgMetrics m = gpg_metrics("messages.po", "# Translation file\n"
"msgid \"\"\n"
"msgstr \"\"\n"
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
"\n"
"msgid \"Hello\"\n"
"msgstr \"Bonjour\"\n"
"\n"
"msgid \"Goodbye\"\n"
"msgstr \"Au revoir\"\n");
ASSERT_TRUE(m.ok);
/* GREEN: PO produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 23 — Regex (.re)
*
* Regex golden histogram: Module:1 (pure-data).
* Extension: ".re" maps to CBM_LANG_REGEX in grammar_cases.
* ══════════════════════════════════════════════════════════════════ */
/* Regex: pattern file → only Module node. */
TEST(probe_regex_module_only) {
GpgMetrics m = gpg_metrics("patterns.re", "(foo|bar)+\n"
"^[a-zA-Z_][a-zA-Z0-9_]*$\n"
"\\d{4}-\\d{2}-\\d{2}\n");
ASSERT_TRUE(m.ok);
/* REAL BUG (NEW class — file-index routing gap): the Regex grammar +
* histogram (grammar_labels regex=Module:1) work via DIRECT extraction
* (a.re), but the ".re" extension has no EXT_TABLE entry in language.c,
* so file-based index_repository never routes it → 0 Module nodes.
* Routing/registration gap, distinct from extraction classes 2/16. RED. */
ASSERT_TRUE(m.modules == 1); /* REAL BUG — .re not routed by file index */
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 24 — SSH Config (ssh_config / sshd_config / config)
*
* SSH Config golden histogram: Module:1 (pure-data).
* Special filenames: "ssh_config", "sshd_config", ".ssh/config" (FILENAME_TABLE).
* Also matches "config" basename in certain contexts (grammar_regression uses "config").
* ══════════════════════════════════════════════════════════════════ */
/* SSH Config: host file via ssh_config filename → only Module node. */
TEST(probe_sshconfig_module_only) {
GpgMetrics m = gpg_metrics("ssh_config", "Host bastion\n"
" HostName 10.0.0.1\n"
" User ubuntu\n"
" IdentityFile ~/.ssh/id_rsa\n"
"\n"
"Host dev\n"
" HostName dev.example.com\n"
" User deploy\n");
ASSERT_TRUE(m.ok);
/* GREEN: SSH config produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 25 — BibTeX (.bib)
*
* BibTeX golden histogram: Module:1 (pure-data).
* Bibliography entries are NOT extracted as structural nodes.
* ══════════════════════════════════════════════════════════════════ */
/* BibTeX: bibliography file → only Module node. */
TEST(probe_bibtex_module_only) {
GpgMetrics m = gpg_metrics("refs.bib", "@article{smith2023,\n"
" author = {Smith, John},\n"
" title = {A Study of Something},\n"
" year = {2023},\n"
" journal = {J. Examples}\n"
"}\n"
"\n"
"@book{jones2020,\n"
" author = {Jones, Alice},\n"
" title = {Reference Book},\n"
" year = {2020}\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: BibTeX produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 26 — FORM (.frm)
*
* FORM golden histogram: Module:1, Variable:1
* The FORM language (symbolic algebra) produces a Variable node for
* one definition. Extension: .frm (CBM_LANG_FORM in EXT_TABLE).
* ══════════════════════════════════════════════════════════════════ */
/* FORM: file with a variable declaration → at least 1 Variable node. */
TEST(probe_form_variable_node) {
GpgMetrics m = gpg_metrics("calc.frm", "Symbol x, y, z;\n"
"Local F = x^2 + y^2 + z^2;\n"
"Print;\n"
".end\n");
ASSERT_TRUE(m.ok);
/* GREEN: FORM produces at least 1 Variable node (histogram: Variable:1). */
ASSERT_TRUE(m.variables >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* FORM: no-crash guard with procedure definition. */
TEST(probe_form_procedure) {
GpgMetrics m = gpg_metrics("proc.frm", "#procedure Square(x)\n"
" id `x'^2 = `x'^2;\n"
"#endprocedure\n"
"\n"
"Symbol a;\n"
"Local expr = a^2;\n"
".end\n");
ASSERT_TRUE(m.ok);
/* GREEN: at least 1 node (Module). */
ASSERT_TRUE(m.total_nodes >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 27 — Linker Script (.ld / .lds)
*
* Linker Script golden histogram: Module:1 (pure-data).
* SECTIONS / MEMORY directives are not extracted as structural nodes.
* ══════════════════════════════════════════════════════════════════ */
/* Linker script: SECTIONS directive → only Module node. */
TEST(probe_linkerscript_module_only) {
GpgMetrics m = gpg_metrics("link.ld", "SECTIONS\n"
"{\n"
" . = 0x10000;\n"
" .text : { *(.text) }\n"
" .data : { *(.data) }\n"
" .bss : { *(.bss) }\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: linker script produces only a Module node. */
ASSERT_TRUE(m.modules == 1);
ASSERT_TRUE(m.variables == 0);
PASS();
}
/* Linker script: MEMORY + SECTIONS → still only Module node. */
TEST(probe_linkerscript_memory_sections) {
GpgMetrics m = gpg_metrics("stm32.ld", "MEMORY\n"
"{\n"
" FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K\n"
" RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K\n"
"}\n"
"SECTIONS\n"
"{\n"
" .isr_vector : { *(.isr_vector) } > FLASH\n"
" .text : { *(.text*) } > FLASH\n"
" .bss : { *(.bss*) } > RAM\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: linker script produces only a Module node. */
ASSERT_TRUE(m.modules >= 1);
ASSERT_TRUE(m.classes == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* SUITE wiring
* ══════════════════════════════════════════════════════════════════ */
SUITE(grammar_probe_g) {
/* HTML */
RUN_TEST(probe_html_minimal_page);
RUN_TEST(probe_html_script_imports);
RUN_TEST(probe_html_no_structural_nodes);
/* XML */
RUN_TEST(probe_xml_element_nodes);
RUN_TEST(probe_xml_multiple_elements);
RUN_TEST(probe_xml_no_crash);
/* CSS */
RUN_TEST(probe_css_module_only);
RUN_TEST(probe_css_at_import);
/* SCSS */
RUN_TEST(probe_scss_variable_node);
RUN_TEST(probe_scss_at_use_imports);
/* Markdown */
RUN_TEST(probe_markdown_section_node);
RUN_TEST(probe_markdown_multi_heading);
RUN_TEST(probe_markdown_no_class_variable);
/* RST */
RUN_TEST(probe_rst_module_only);
RUN_TEST(probe_rst_no_crash);
/* JSON */
RUN_TEST(probe_json_variable_nodes);
RUN_TEST(probe_json_more_variables);
/* JSON5 */
RUN_TEST(probe_json5_module_only);
/* YAML */
RUN_TEST(probe_yaml_variable_nodes);
RUN_TEST(probe_yaml_three_keys);
/* TOML */
RUN_TEST(probe_toml_table_and_key);
RUN_TEST(probe_toml_multiple_tables);
/* INI */
RUN_TEST(probe_ini_section_and_key);
RUN_TEST(probe_ini_conf_extension);
/* CSV */
RUN_TEST(probe_csv_module_only);
/* SQL */
RUN_TEST(probe_sql_variable_node);
RUN_TEST(probe_sql_insert_select);
/* SOQL */
RUN_TEST(probe_soql_module_only);
/* SOSL */
RUN_TEST(probe_sosl_module_only);
/* DotEnv */
RUN_TEST(probe_dotenv_module_only);
RUN_TEST(probe_dotenv_local_suffix);
/* gitignore */
RUN_TEST(probe_gitignore_module_only);
/* gitattributes */
RUN_TEST(probe_gitattributes_module_only);
/* Properties */
RUN_TEST(probe_properties_module_only);
/* Requirements */
RUN_TEST(probe_requirements_module_only);
RUN_TEST(probe_requirements_depends_on_edge);
/* Diff */
RUN_TEST(probe_diff_module_only);
/* PO */
RUN_TEST(probe_po_module_only);
/* Regex */
RUN_TEST(probe_regex_module_only);
/* SSH Config */
RUN_TEST(probe_sshconfig_module_only);
/* BibTeX */
RUN_TEST(probe_bibtex_module_only);
/* FORM */
RUN_TEST(probe_form_variable_node);
RUN_TEST(probe_form_procedure);
/* Linker Script */
RUN_TEST(probe_linkerscript_module_only);
RUN_TEST(probe_linkerscript_memory_sections);
}