/* * test_lang_contract.c — Per-language GRAPH-CONTRACT regression suite. * * Unlike the in-process unit tests (which exercise tiny inline snippets and * passed while real behavioral bugs shipped), this suite indexes a per-language * fixture through the FULL pipeline into a real graph DB and asserts INVARIANT * contracts on the result: expected node/edge TYPES are present, calls are * attributed to the calling Function (not the file/Module), resolution happens, * and extraction does not crash. * * Design notes: * - INVARIANTS, not golden snapshots: edge counts are non-deterministic * (parallel LSH/similarity/resolve), so we assert PRESENCE + floors, never * exact counts. This survives grammar refreshes without churn. * - Crashes are caught via a FORKED subprocess + exit-signal check: ASan does * NOT intercept SIGBUS, so an in-process crash would kill the test runner. */ #include "../src/foundation/compat.h" #include "test_framework.h" #include "test_helpers.h" #include "cbm.h" #include "grammar_cases.h" #include #include #include #include #include #include #include #include #include #if !defined(_WIN32) #include /* fork/waitpid crash isolation — POSIX only */ #endif /* ── Shared harness: index one fixture file through the production pipeline ── */ typedef struct { char tmpdir[256]; char dbpath[512]; char *project; cbm_mcp_server_t *srv; } LangProj; typedef struct { const char *name; /* relative filename, may include '/' for subdirs */ const char *content; } LangFile; /* Given lp->tmpdir already populated with source files, run the production * index_repository flow and open the resulting graph DB (NULL on failure). * Split out of lang_index_files so a caller can interpose between writing files * and indexing (e.g. the FILE_CHANGES_WITH test git-inits + commits first). */ /* Normalize backslashes to forward slashes in place. cbm_mkdtemp on Windows * (msys2) yields a native path with backslashes (e.g. D:\a\_temp\...); those * break the JSON repo_path ("\a"/"\t" are invalid JSON escapes → index fails → * count=-1) and produce mixed-separator paths in git -C args. Forward slashes * are valid in JSON and accepted by Windows file APIs and git. */ static void lc_to_fwd_slashes(char *p) { for (; *p; p++) { if (*p == '\\') { *p = '/'; } } } static cbm_store_t *lang_open_indexed(LangProj *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); } /* Write each fixture file into a fresh temp project, index it via the MCP * production flow (discover -> extract -> registry -> resolve -> dump), and open * the resulting graph DB. Returns the store (NULL on any failure). */ static cbm_store_t *lang_index_files(LangProj *lp, const LangFile *files, int nfiles) { memset(lp, 0, sizeof(*lp)); snprintf(lp->tmpdir, sizeof(lp->tmpdir), "/tmp/cbm_lc_XXXXXX"); if (!cbm_mkdtemp(lp->tmpdir)) { return NULL; } lc_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); /* Create parent directories if the fixture name embeds subdirs. * Use mkdir_p (not mkdir) so multi-level paths like * "pkg/validation/x.go" create every intermediate directory. */ char *slash = strrchr(path, '/'); if (slash && slash > path + strlen(lp->tmpdir)) { *slash = '\0'; cbm_mkdir_p(path, 0755); *slash = '/'; } /* Binary mode: keep fixture line endings exactly as written ("\n"). * Windows text mode rewrites "\n"→"\r\n", which makes line-ending- * sensitive grammars under-extract (below_min / calls_breadth). */ FILE *f = fopen(path, "wb"); if (!f) { return NULL; } fputs(files[i].content, f); fclose(f); } return lang_open_indexed(lp); } /* Convenience: index a single fixture file. */ static cbm_store_t *lang_index(LangProj *lp, const char *filename, const char *content) { LangFile f = {filename, content}; return lang_index_files(lp, &f, 1); } static void lang_cleanup(LangProj *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]; char shm[600]; snprintf(wal, sizeof(wal), "%s-wal", lp->dbpath); unlink(wal); snprintf(shm, sizeof(shm), "%s-shm", lp->dbpath); unlink(shm); } /* Count nodes of `label` with >=1 outbound edge. Returns -1 on query error. */ static int label_with_outbound(cbm_store_t *store, const char *project, const char *label) { cbm_search_params_t p = {0}; p.project = project; p.label = label; p.min_degree = 1; p.max_degree = -1; p.limit = 50; cbm_search_output_t out = {0}; int n = -1; if (cbm_store_search(store, &p, &out) == CBM_STORE_OK) { n = out.count; } cbm_store_search_free(&out); return n; } /* Callables (Function OR Method) that have >=1 outbound edge — the cross-language * "calls are attributed to the calling routine, not lumped on the file/Module * node" invariant (a mis-attributed call would leave the callable at degree 0). */ static int callables_with_outbound(cbm_store_t *store, const char *project) { int fn = label_with_outbound(store, project, "Function"); int mt = label_with_outbound(store, project, "Method"); return (fn < 0 ? 0 : fn) + (mt < 0 ? 0 : mt); } /* Count nodes carrying `label`. Returns -1 on query error. */ static int 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; } /* Count "type-like" nodes across the labels different languages use for a * user-defined type (class/struct/interface/enum/trait/type alias). Lets a * contract assert "the type was modeled" without hard-coding one label. */ static int type_like_nodes(cbm_store_t *store, const char *project) { static const char *labels[] = {"Class", "Struct", "Interface", "Enum", "Trait", "Type", NULL}; int total = 0; for (int i = 0; labels[i]; i++) { int n = count_label(store, project, labels[i]); if (n > 0) { total += n; } } return total; } /* Run cbm_extract_file in a forked child; return true if the child died from a * signal (SIGBUS/SIGSEGV/...). ASan does not install a SIGBUS handler, so an * in-process crash would terminate the whole test runner — forking isolates it * and lets us assert "extraction must not crash" deterministically. * * Windows (msys2) has no fork()/waitpid(): run in-process. A genuine crash there * aborts the runner (a hard, visible failure), and the fork-isolated check still * runs on the POSIX CI legs, so coverage is preserved. */ static bool extract_crashes(const char *content, CBMLanguage lang, const char *relpath) { #if defined(_WIN32) CBMFileResult *r = cbm_extract_file(content, (int)strlen(content), lang, "lc", relpath, 0, NULL, NULL); if (r) { cbm_free_result(r); } return false; #else fflush(NULL); pid_t pid = fork(); if (pid < 0) { return false; /* cannot fork — do not flag a crash we can't observe */ } if (pid == 0) { CBMFileResult *r = cbm_extract_file(content, (int)strlen(content), lang, "lc", relpath, 0, NULL, NULL); if (r) { cbm_free_result(r); } _exit(0); } int status = 0; (void)waitpid(pid, &status, 0); return WIFSIGNALED(status); #endif } /* ══════════════════════════════════════════════════════════════════ * KNOWN-BUG CONTRACTS (must FAIL until fixed) — see P2 * ══════════════════════════════════════════════════════════════════ */ /* Kotlin 0-IMPORTS bug — ROOT CAUSE: extraction layer. The Kotlin AST nests * imports as source_file -> import_list -> import_header*, but the extractor * matched the "import" keyword token as a direct child of root -> 0 imports. * This probe asserts cbm_extract_file captures them; it is the precise, reliable * reproduction + fix-verification (graph-layer IMPORTS-edge formation depends on * Maven/Gradle module resolution and is verified on the real ktor repo in the * P5 scale tier — a synthetic small fixture can't faithfully exercise it). */ static const char *KT_SRC = "import kotlin.io.path.Path\n" "import java.util.ArrayList\n" "\n" "fun build(): ArrayList {\n" " val xs = ArrayList()\n" " xs.add(Path(\"/tmp/x\").toString())\n" " return xs\n" "}\n"; TEST(contract_kotlin_imports_extracted) { CBMFileResult *r = cbm_extract_file(KT_SRC, (int)strlen(KT_SRC), CBM_LANG_KOTLIN, "lc", "a.kt", 0, NULL, NULL); ASSERT_NOT_NULL(r); int n = r->imports.count; cbm_free_result(r); ASSERT_TRUE(n >= 1); /* 2 imports in the fixture */ PASS(); } /* C: a function that calls another must yield a CALLS edge attributed to the * CALLING FUNCTION (not lumped onto the file/Module node). */ static const char *C_SRC = "static int helper(int x) { return x + 1; }\n" "int run(int y) {\n" " int a = helper(y);\n" " return helper(a);\n" "}\n"; TEST(contract_c_calls_attributed_to_function) { LangProj lp; cbm_store_t *store = lang_index(&lp, "a.c", C_SRC); int calls = store ? cbm_store_count_edges_by_type(store, lp.project, "CALLS") : -1; int fn_callers = store ? callables_with_outbound(store, lp.project) : -1; lang_cleanup(&lp, store); ASSERT_TRUE(calls >= 1); /* run() -> helper() */ ASSERT_TRUE(fn_callers >= 1); /* the CALLS must hang off a Function, not Module */ PASS(); } /* Java: extraction must not crash on a real-world construct mix (enhanced-for + * method reference + method chain + pattern instanceof) — reproduces the SIGBUS. */ static const char *JAVA_SRC = "package zip;\n" "import java.io.Closeable;\n" "import java.lang.ref.Cleaner.Cleanable;\n" "import java.util.ArrayList;\n" "import java.util.List;\n" "class Tracker {\n" " private final List clean = new ArrayList<>();\n" " private final List close = new ArrayList<>();\n" " void assertAllClosed() throws Exception {\n" " for (Closeable closeable : this.close) {\n" " closeable.close();\n" " }\n" " this.clean.forEach(Cleanable::clean);\n" " }\n" " void added(Object obj, Cleanable cleanable) {\n" " if (obj instanceof Closeable closeable) {\n" " this.close.add(closeable);\n" " }\n" " }\n" "}\n"; TEST(contract_java_extract_no_crash) { bool crashed = extract_crashes(JAVA_SRC, CBM_LANG_JAVA, "Tracker.java"); ASSERT_TRUE(!crashed); /* CURRENTLY MAY FAIL: SIGBUS in the Java LSP */ PASS(); } /* ══════════════════════════════════════════════════════════════════ * RICH PER-LANGUAGE CONTRACTS (P3) — invariants that must hold for each * hybrid-LSP language on a clean same-file fixture: resolution produces * CALLS, those CALLS are attributed to a callable (not the Module), and * user-defined types are modeled. These guard against silent per-language * regressions (a grammar/LSP change that stops resolving or mis-attributes). * ══════════════════════════════════════════════════════════════════ */ typedef struct { int ok; /* graph DB opened */ int calls; int callers; /* callables (Function/Method) with an outbound edge */ int types; /* type-like nodes */ int imports; } LangMetrics; static LangMetrics lang_metrics(const LangFile *files, int nfiles) { LangProj lp; cbm_store_t *store = lang_index_files(&lp, files, nfiles); LangMetrics m = {0}; if (store) { m.ok = 1; m.calls = cbm_store_count_edges_by_type(store, lp.project, "CALLS"); m.callers = callables_with_outbound(store, lp.project); m.types = type_like_nodes(store, lp.project); m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS"); } lang_cleanup(&lp, store); return m; } /* Go: same-package function call. */ TEST(contract_go_calls) { static const LangFile f[] = {{"svc.go", "package svc\n\n" "func helper(x int) int { return x + 1 }\n\n" "func run(y int) int {\n return helper(helper(y))\n}\n"}}; LangMetrics m = lang_metrics(f, 1); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); PASS(); } /* Rust: struct + impl methods + method call. */ TEST(contract_rust_methods) { static const LangFile f[] = { {"calc.rs", "struct Calc {\n base: i32,\n}\n\n" "impl Calc {\n" " fn helper(&self, x: i32) -> i32 { self.base + x }\n" " fn run(&self, y: i32) -> i32 { self.helper(y) }\n}\n\n" "fn main() {\n let c = Calc { base: 1 };\n let _ = c.run(2);\n}\n"}}; LangMetrics m = lang_metrics(f, 1); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); ASSERT_TRUE(m.types >= 1); /* struct Calc */ PASS(); } /* C#: class with methods + intra-class call. */ TEST(contract_csharp_methods) { static const LangFile f[] = {{"Calc.cs", "namespace App {\n" " class Calc {\n" " public int Helper(int x) { return x + 1; }\n" " public int Run(int y) { return Helper(y); }\n" " }\n}\n"}}; LangMetrics m = lang_metrics(f, 1); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); ASSERT_TRUE(m.types >= 1); /* class Calc */ PASS(); } /* PHP: class with methods + $this-> call. */ TEST(contract_php_methods) { static const LangFile f[] = { {"Calc.php", "helper($y); }\n}\n"}}; LangMetrics m = lang_metrics(f, 1); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); ASSERT_TRUE(m.types >= 1); /* class Calc */ PASS(); } /* Java: class with methods + intra-class call (single file — no crash risk). */ TEST(contract_java_methods) { static const LangFile f[] = {{"Calc.java", "package app;\n\nclass Calc {\n" " int helper(int x) { return x + 1; }\n" " int run(int y) { return helper(y); }\n}\n"}}; LangMetrics m = lang_metrics(f, 1); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); ASSERT_TRUE(m.types >= 1); /* class Calc */ PASS(); } /* Kotlin: class with methods + intra-class call. */ TEST(contract_kotlin_methods) { static const LangFile f[] = {{"Calc.kt", "class Calc {\n" " fun helper(x: Int): Int = x + 1\n" " fun run(y: Int): Int = helper(y)\n}\n"}}; LangMetrics m = lang_metrics(f, 1); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); ASSERT_TRUE(m.types >= 1); /* class Calc */ PASS(); } /* Python: a RELATIVE import resolves to a sibling module — produces an IMPORTS * edge without needing a package manifest (resolve_relative_import), so this is * a reliable small-fixture graph-layer IMPORTS contract. */ TEST(contract_python_relative_import) { static const LangFile f[] = { {"util.py", "def helper(x):\n return x + 1\n"}, {"main.py", "from .util import helper\n\n\ndef run(y):\n return helper(y)\n"}}; LangMetrics m = lang_metrics(f, 2); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); ASSERT_TRUE(m.imports >= 1); /* IMPORTS edge for `from .util import helper` */ PASS(); } /* TypeScript: a relative import resolves to a sibling module → IMPORTS edge. */ TEST(contract_typescript_relative_import) { static const LangFile f[] = { {"util.ts", "export function helper(x: number): number {\n return x + 1;\n}\n"}, {"main.ts", "import { helper } from './util';\n\n" "export function run(y: number): number {\n return helper(y);\n}\n"}}; LangMetrics m = lang_metrics(f, 2); ASSERT_TRUE(m.ok); ASSERT_TRUE(m.calls >= 1); ASSERT_TRUE(m.callers >= 1); ASSERT_TRUE(m.imports >= 1); /* IMPORTS edge for `import { helper } from './util'` */ PASS(); } /* ══════════════════════════════════════════════════════════════════ * GRAPH-LEVEL BREADTH (P4) — every grammar's fixture, indexed through the * full pipeline, must produce its defs as graph nodes. Indexes all fixtures * in ONE pass (each in its own gNNN/ subdir to avoid filename collisions). * * Discover maps files to a language by extension, so grammars whose extension * collides with another's (e.g. .m objc/matlab, .yaml yaml/k8s) — or whose * files discover intentionally skips — can't be isolated through index_repository. * Those are allow-listed here and remain covered at the EXTRACTION level by * test_grammar_regression.c (which uses an explicit language, bypassing discover). * ══════════════════════════════════════════════════════════════════ */ enum { GRAMMAR_BREADTH_MAX = 300, GRAMMAR_PATH_BUF = 96 }; /* Grammars not reachable as standalone indexed files via index_repository; * covered at the extraction level by test_grammar_regression.c instead. Reasons: * nasm — fixture ext .asm; discover maps .nasm (.asm is ambiguous across assemblers) * dotenv — discover does not route .env (often gitignored/secret) * jsdoc, regex — not standalone source files; the grammars parse content embedded in JS * gitignore — discover consumes .gitignore AS ignore rules, not as an indexed source * gitattributes — same: consumed as a git metadata file * sshconfig — discover detects ssh_config / .ssh/config, not the generic name "config" */ static bool grammar_graph_allowlisted(const char *name) { static const char *allow[] = {"nasm", "dotenv", "jsdoc", "regex", "gitignore", "gitattributes", "sshconfig", NULL}; for (int i = 0; allow[i]; i++) { if (strcmp(allow[i], name) == 0) { return true; } } return false; } /* Non-structural node count for a file (excludes the File/Module/Folder/Package * wrappers the pipeline always creates) — i.e. how many defs reached the graph. */ static int def_nodes_for_file(cbm_store_t *store, const char *project, const char *rel) { cbm_node_t *nodes = NULL; int count = 0; if (cbm_store_find_nodes_by_file(store, project, rel, &nodes, &count) != CBM_STORE_OK) { return -1; } int defs = 0; for (int i = 0; i < count; i++) { const char *l = nodes[i].label; if (l && strcmp(l, "File") != 0 && strcmp(l, "Module") != 0 && strcmp(l, "Folder") != 0 && strcmp(l, "Package") != 0 && strcmp(l, "Directory") != 0) { defs++; } } cbm_store_free_nodes(nodes, count); return count == 0 ? -1 : defs; /* -1 signals "file not indexed at all" */ } /* On a breadth failure, explain WHY: does direct extraction (no pipeline, no * timeout) yield defs, and what node labels did the pipeline actually create for * the file? Distinguishes "extraction itself broke" from "extraction works but * the def didn't reach the graph / got an unexpected label". */ static void breadth_diag(cbm_store_t *store, const char *project, const char *rel, const GrammarCase *c) { CBMFileResult *dr = cbm_extract_file(c->src, (int)strlen(c->src), c->lang, "lc", c->path, 0, NULL, NULL); int direct = dr ? dr->defs.count : -1; const char *direct_label = (dr && dr->defs.count > 0) ? dr->defs.items[0].label : "-"; char labels[256] = {0}; cbm_node_t *nodes = NULL; int count = 0; if (cbm_store_find_nodes_by_file(store, project, rel, &nodes, &count) == CBM_STORE_OK) { for (int i = 0; i < count && strlen(labels) < sizeof(labels) - 40; i++) { char one[48]; snprintf(one, sizeof(one), "%s ", nodes[i].label ? nodes[i].label : "?"); strncat(labels, one, sizeof(labels) - strlen(labels) - 1); } } cbm_store_free_nodes(nodes, count); fprintf(stderr, " └─ direct_extract_defs=%d (label0=%s) graph_labels=[%s]\n", direct, direct_label ? direct_label : "(null)", labels); if (dr) { cbm_free_result(dr); } } TEST(contract_all_grammars_in_graph) { int n = (int)CBM_GRAMMAR_CASES_COUNT; if (n > GRAMMAR_BREADTH_MAX) { n = GRAMMAR_BREADTH_MAX; } static char names[GRAMMAR_BREADTH_MAX][GRAMMAR_PATH_BUF]; LangFile files[GRAMMAR_BREADTH_MAX] = {0}; /* zero-init: GCC -Werror=maybe-uninitialized */ for (int i = 0; i < n; i++) { snprintf(names[i], sizeof(names[i]), "g%03d/%s", i, CBM_GRAMMAR_CASES[i].path); files[i].name = names[i]; files[i].content = CBM_GRAMMAR_CASES[i].src; } LangProj lp; cbm_store_t *store = lang_index_files(&lp, files, n); int total_nodes = store ? cbm_store_count_nodes(store, lp.project) : -1; int not_indexed = 0; int below_min = 0; int checked = 0; for (int i = 0; i < n; i++) { const GrammarCase *c = &CBM_GRAMMAR_CASES[i]; if (grammar_graph_allowlisted(c->name)) { continue; } char rel[GRAMMAR_PATH_BUF]; snprintf(rel, sizeof(rel), "g%03d/%s", i, c->path); int defs = store ? def_nodes_for_file(store, lp.project, rel) : -1; checked++; if (defs < 0) { fprintf(stderr, " [GRAPH-BREADTH] %-14s NOT INDEXED (0 nodes for %s)\n", c->name, rel); breadth_diag(store, lp.project, rel, c); not_indexed++; } else if (defs < c->min_defs) { fprintf(stderr, " [GRAPH-BREADTH] %-14s defs-in-graph=%d < min=%d\n", c->name, defs, c->min_defs); breadth_diag(store, lp.project, rel, c); below_min++; } } fprintf(stderr, " [GRAPH-BREADTH] checked=%d not_indexed=%d below_min=%d total_nodes=%d (of %d " "grammars)\n", checked, not_indexed, below_min, total_nodes, n); lang_cleanup(&lp, store); ASSERT_TRUE(total_nodes > 0); ASSERT_EQ(not_indexed, 0); ASSERT_EQ(below_min, 0); PASS(); } /* ══════════════════════════════════════════════════════════════════ * CALLS-EDGE BREADTH (P5) — beyond the 9 hybrid-LSP languages, verify that a * same-file caller->callee resolves to a CALLS edge through the generic * name-based resolver (pass_calls.c). Languages where it does NOT are real * edge gaps (existing bugs) — flagged here with expect_calls=false + a note, * reproducibly, for the next (bugfixing) phase. Fixtures authored per-language * to parse cleanly with the vendored grammar. * ══════════════════════════════════════════════════════════════════ */ typedef struct { const char *lang; const char *filename; const char *content; bool expect_calls; /* true: must resolve a CALLS edge; false: known edge gap */ const char *gap_note; /* why it doesn't resolve (for known gaps) */ } CallCase; static const CallCase CALL_CASES[] = { {"ruby", "a.rb", "def helper\n 42\nend\n\ndef run\n helper()\nend\n", true, NULL}, {"lua", "a.lua", "function helper(x)\n return x + 1\nend\n\nfunction run()\n return helper(41)\nend\n", true, NULL}, {"swift", "a.swift", "func helper() -> Int {\n return 42\n}\n\nfunc run() {\n let value = helper()\n " "print(value)\n}\n", true, NULL}, {"dart", "a.dart", "void helper() {\n print('helper');\n}\n\nvoid run() {\n helper();\n}\n", false, "selector call node carries no callee field; no dart branch in extract_calls.c"}, {"scala", "a.scala", "def helper(): Int =\n 21 + 21\n\ndef run(): Int =\n helper() * 2\n", true, NULL}, {"bash", "a.sh", "helper() {\n echo \"doing work\"\n}\n\nrun() {\n helper\n}\n", true, NULL}, {"perl", "a.pl", "#!/usr/bin/perl\nuse strict;\nuse warnings;\n\nsub helper {\n my ($name) = @_;\n " "return \"Hello, $name\";\n}\n\nsub run {\n my $greeting = helper(\"world\");\n print " "\"$greeting\\n\";\n return;\n}\n\nrun();\n", true, NULL}, {"r", "a.R", "helper <- function(x) {\n x * 2\n}\n\nrun <- function() {\n helper(21)\n}\n", true, NULL}, {"julia", "a.jl", "function helper(x)\n return x + 1\nend\n\nfunction run(n)\n return helper(n)\nend\n", true, NULL}, {"zig", "a.zig", "fn helper() void {}\n\npub fn run() void {\n helper();\n}\n", true, NULL}, {"gdscript", "a.gd", "func helper() -> int:\n\treturn 42\n\n\nfunc run() -> void:\n\tvar value = " "helper()\n\tprint(value)\n", true, NULL}, {"groovy", "a.groovy", "def helper() {\n println \"helping\"\n}\n\ndef run() {\n helper()\n}\n", false, "function_call callee not on a function/name field and first child is not 'identifier'; no " "groovy branch in extract_calls.c"}, {"elixir", "a.ex", "defmodule Sample do\n def helper(x) do\n x + 1\n end\n\n def run do\n helper(41)\n " "end\nend\n", true, NULL}, {"ocaml", "a.ml", "let helper x = x + 1\n\nlet run () =\n let result = helper 41 in\n print_int result\n", true, NULL}, {"gleam", "a.gleam", "pub fn helper(x: Int) -> Int {\n x + 1\n}\n\npub fn run() -> Int {\n helper(2)\n}\n", true, NULL}, {"crystal", "a.cr", "def helper(name : String) : String\n \"hello, #{name}\"\nend\n\ndef run\n message = " "helper(\"world\")\n puts message\nend\n\nrun\n", true, NULL}, {"haskell", "a.hs", "helper :: Int -> Int\nhelper x = x + 1\n\nrun :: Int\nrun = helper 41\n", true, NULL}, {"fortran", "a.f90", "function helper(x) result(y)\n integer, intent(in) :: x\n integer :: y\n y = x + " "1\nend function helper\n\nfunction run(n) result(total)\n integer, intent(in) :: n\n " "integer :: total\n total = helper(n) + helper(n + 1)\nend function run\n", true, NULL}, {"pascal", "a.pas", "procedure Helper(x: Integer);\nbegin\n WriteLn(x);\nend;\n\nprocedure Run;\nbegin\n " "Helper(1);\nend;\n", true, NULL}, {"tcl", "a.tcl", "proc helper {x} {\n return [expr {$x * 2}]\n}\n\nproc run {} {\n set result [helper " "21]\n puts $result\n}\n", true, NULL}, {"solidity", "a.sol", "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nfunction helper(uint256 x) pure " "returns (uint256) {\n return x + 1;\n}\n\nfunction run(uint256 y) pure returns (uint256) " "{\n return helper(y);\n}\n", false, "call_expression callee not resolved by generic field/first-child extraction; no solidity " "branch in extract_calls.c"}, {"commonlisp", "a.lisp", "(defun helper (x)\n (* x 2))\n\n(defun run ()\n (helper 21))\n", false, "list_lit call head is sym_lit not identifier; no commonlisp branch in extract_callee_name"}, {"powershell", "a.ps1", "function helper {\n Write-Output 'hi'\n}\n\nfunction run {\n helper\n}\n", false, "command node child is command_name not identifier; extract_scripting_callee handles MATLAB " "not PowerShell"}, /* The remaining code grammars (extends CALLS-edge breadth to ALL 66 code * grammars). expected_calls=false marks an already-root-caused gap. */ {"ada", "a.adb", "procedure Run is\n procedure Helper is\n begin\n null;\n end Helper;\nbegin\n " "Helper;\nend Run;\n", false, "procedure_call_statement callee did not resolve to a CALLS edge (empirically confirmed gap; " "no Ada branch in extract_calls.c)"}, {"apex", "A.cls", "public class A {\n static void helper() {\n System.debug('helper');\n }\n\n " "static void run() {\n helper();\n }\n}\n", true, NULL}, {"awk", "a.awk", "function helper(x) {\n return x + 1\n}\n\nfunction run() {\n print helper(3)\n}\n", true, NULL}, {"cairo", "a.cairo", "fn helper(x: felt252) -> felt252 {\n x + 1\n}\n\nfn run() -> felt252 {\n " "helper(41)\n}\n", true, NULL}, {"clojure", "a.clj", "(defn helper [] 42)\n\n(defn run [] (helper))\n", false, "lisp: call is a list_lit whose head is a sym_lit (not a field, not a first-child " "'identifier'); no lisp branch in extract_callee_name"}, {"cuda", "a.cu", "__device__ int helper(int x) {\n return x * 2;\n}\n\n__global__ void run(int *out) {\n " "out[0] = helper(21);\n}\n", true, NULL}, {"d", "a.d", "int helper(int x)\n{\n return x + 1;\n}\n\nvoid run()\n{\n int y = helper(41);\n}\n", true, NULL}, {"emacslisp", "a.el", "(defun helper (x)\n \"Add one to X.\"\n (+ x 1))\n\n(defun run ()\n \"Call helper.\"\n " "(helper 41))\n", false, "lisp: call is a 'list' whose head is a 'symbol'; generic resolver wants a first-child " "'identifier'"}, {"erlang", "a.erl", "-module(a).\n-export([run/0]).\n\nhelper(X) -> X + 1.\n\nrun() -> helper(41).\n", true, NULL}, {"fennel", "a.fnl", "(fn helper [x]\n (+ x 1))\n\n(fn run [n]\n (helper n))\n", false, "lisp: call is a 'list' whose head is a 'symbol'; no fennel branch in extract_callee_name"}, {"fish", "a.fish", "function helper\n echo \"helping\"\nend\n\nfunction run\n helper\nend\n", true, NULL}, {"fsharp", "a.fs", "let helper x = x + 1\n\nlet run () = helper 41\n", false, "application_expression callee head is a 'long_identifier_or_op' wrapper, not a bare " "identifier/field; no fsharp callee branch"}, {"glsl", "a.glsl", "float helper(float x) {\n return x * 2.0;\n}\n\nvoid run() {\n float y = " "helper(3.0);\n}\n", true, NULL}, {"hare", "a.ha", "fn helper() void = void;\n\nexport fn run() void = {\n\thelper();\n};\n", true, NULL}, {"hlsl", "a.hlsl", "float helper(float x)\n{\n return x * 2.0;\n}\n\nfloat run(float v)\n{\n return " "helper(v) + 1.0;\n}\n", true, NULL}, {"ispc", "a.ispc", "static inline uniform float helper(uniform float x) {\n return x * 2.0f;\n}\n\nexport " "void run(uniform float in[], uniform float out[], uniform int n) {\n foreach (i = 0 ... " "n) {\n out[i] = helper(in[i]);\n }\n}\n", true, NULL}, {"luau", "a.luau", "function helper(x)\n\treturn x + 1\nend\n\nfunction run(n)\n\treturn helper(n) * 2\nend\n", true, NULL}, {"matlab", "a.m", "function run()\n helper();\nend\n\nfunction helper()\n disp('hi');\nend\n", true, "watch: .m extension collides with Objective-C under discover (content-sniffed)"}, {"odin", "a.odin", "package fixture\n\nhelper :: proc() -> int {\n\treturn 42\n}\n\nrun :: proc() {\n\tx := " "helper()\n\t_ = x\n}\n", true, NULL}, {"racket", "a.rkt", "#lang racket\n\n(define (helper x)\n (+ x 1))\n\n(define (run)\n (helper 41))\n", false, "lisp: call is a 'list' whose head is a 'symbol' (grammar has no 'identifier' node); no " "racket branch"}, {"rescript", "a.res", "let helper = (x) => x + 1\n\nlet run = () => helper(41)\n", false, "call_expression 'function' field is a 'value_identifier' (not in extract_callee_from_fields' " "accepted type list)"}, {"scheme", "a.scm", "(define (helper x)\n (* x 2))\n\n(define (run)\n (helper 21))\n", false, "lisp: call is a 'list' whose head is a 'symbol'; no scheme branch in extract_callee_name"}, {"slang", "a.slang", "void helper()\n{\n int x = 1;\n}\n\nvoid run()\n{\n helper();\n}\n", true, NULL}, {"squirrel", "a.nut", "function helper(x) {\n return x + 1;\n}\n\nfunction run() {\n return helper(41);\n}\n", true, NULL}, {"starlark", "a.bzl", "def helper(x):\n return x + 1\n\ndef run():\n return helper(41)\n", true, NULL}, {"sway", "a.sw", "fn helper() {}\n\nfn run() {\n helper();\n}\n", true, NULL}, {"teal", "a.tl", "local function helper(x: number): number\n return x + 1\nend\n\nlocal function run(): " "number\n return helper(41)\nend\n", true, NULL}, {"vimscript", "a.vim", "function! Helper() abort\n return 1\nendfunction\n\nfunction! Run() abort\n call " "Helper()\nendfunction\n", true, NULL}, {"wgsl", "a.wgsl", "fn helper() -> f32 {\n return 1.0;\n}\n\nfn run() -> f32 {\n return helper();\n}\n", false, "callee nested in type_constructor_or_function_call_expression -> type_declaration -> " "identifier; no field, first child is type_declaration"}, {"zsh", "a.zsh", "function helper {\n print \"helping\"\n}\n\nfunction run {\n helper\n}\n", true, NULL}, }; TEST(contract_calls_breadth) { /* EVERY language must resolve a same-file caller->callee into a CALLS edge. * No skips/allowlists: a language that does not is a hard FAILURE that * reproduces the bug (the gap_note explains the known cause for the ones we * have already root-caused). These stay RED until fixed. */ int n = (int)(sizeof(CALL_CASES) / sizeof(CALL_CASES[0])); int failures = 0; for (int i = 0; i < n; i++) { const CallCase *c = &CALL_CASES[i]; const LangFile f = {c->filename, c->content}; LangMetrics m = lang_metrics(&f, 1); if (!(m.ok && m.calls >= 1 && m.callers >= 1)) { fprintf(stderr, " [CALLS-BREADTH] FAIL %-11s ok=%d calls=%d callers=%d%s%s\n", c->lang, m.ok, m.calls, m.callers, c->gap_note ? " — " : "", c->gap_note ? c->gap_note : ""); failures++; } } fprintf(stderr, " [CALLS-BREADTH] %d langs: %d FAILURES (each = a language that does not " "resolve a same-file CALLS edge)\n", n, failures); ASSERT_EQ(failures, 0); PASS(); } /* ══════════════════════════════════════════════════════════════════ * CROSS-CUTTING / SEMANTIC EDGE-TYPE PRESENCE CONTRACTS (P6) * * CALLS (P3/P5) and IMPORTS (test_grammar_imports.c) cover the two * highest-volume edge types. This section guards the REMAINING graph * edge types the pipeline emits, each with the minimal fixture that * triggers it through the SAME production index_repository flow * (mode=full, so the similarity + semantic predump passes run): * * structural DEFINES, DEFINES_METHOD, CONTAINS_FILE * type-relationship INHERITS, IMPLEMENTS, DECORATES * test-mapping TESTS_FILE * service/dataflow HANDLES, HTTP_CALLS, ASYNC_CALLS, DATA_FLOWS * similarity SIMILAR_TO, SEMANTICALLY_RELATED * * We assert PRESENCE (count >= floor), never an exact count — edge * counts are non-deterministic (parallel LSH / similarity / resolve). * A regression that stops emitting any of these turns its test RED. * Fixtures are reasoned from the producing pass (and SIMILAR_TO / * SEMANTICALLY_RELATED were also verified end-to-end) so these are * GREEN contracts: a failure here is a real edge-production regression, * not a known gap. * ══════════════════════════════════════════════════════════════════ */ /* Every graph edge type the pipeline can emit — used to print a histogram * when an edge contract fails, so a regression shows exactly what WAS * produced instead of the missing type. */ static const char *ALL_EDGE_TYPES[] = { "CALLS", "CONFIGURES", "CONTAINS_FILE", "CONTAINS_FOLDER", "DATA_FLOWS", "DECORATES", "DEFINES", "DEFINES_METHOD", "DEPENDS_ON", "FILE_CHANGES_WITH", "GRAPHQL_CALLS", "GRPC_CALLS", "HANDLES", "HTTP_CALLS", "IMPLEMENTS", "IMPORTS", "INHERITS", "INFRA_MAPS", "OVERRIDE", "SEMANTICALLY_RELATED", "SIMILAR_TO", "TESTS_FILE", "TESTS", "TRPC_CALLS", "USAGE", "ASYNC_CALLS", NULL}; static void dump_edge_histogram(cbm_store_t *store, const char *project) { if (!store) { fprintf(stderr, " └─ (no graph DB)\n"); return; } char line[640] = {0}; for (int i = 0; ALL_EDGE_TYPES[i]; i++) { int c = cbm_store_count_edges_by_type(store, project, ALL_EDGE_TYPES[i]); if (c > 0 && strlen(line) < sizeof(line) - 48) { char one[64]; snprintf(one, sizeof(one), "%s=%d ", ALL_EDGE_TYPES[i], c); strncat(line, one, sizeof(line) - strlen(line) - 1); } } fprintf(stderr, " └─ edges produced: [%s]\n", line[0] ? line : "(none)"); } /* Index `files`, assert the named edge type appears at least `floor` times. * On failure, dump the full edge histogram so a regression is diagnosable. */ static int edge_present(const LangFile *files, int nfiles, const char *edge, int floor) { LangProj lp; cbm_store_t *store = lang_index_files(&lp, files, nfiles); int got = store ? cbm_store_count_edges_by_type(store, lp.project, edge) : -1; if (got < floor) { fprintf(stderr, " [EDGE] FAIL %-20s count=%d expected>=%d\n", edge, got, floor); dump_edge_histogram(store, lp.project); } lang_cleanup(&lp, store); return got >= floor; } /* DEFINES — File -> definition, once per top-level def (purely structural). */ TEST(contract_edge_defines) { static const LangFile f[] = {{"main.py", "def greet(name):\n return 'Hello ' + name\n\n\n" "def farewell(name):\n return 'Goodbye ' + name\n\n\n" "def main():\n msg = greet('World')\n" " msg2 = farewell('World')\n print(msg, msg2)\n"}}; ASSERT_TRUE(edge_present(f, 1, "DEFINES", 3)); /* greet, farewell, main */ PASS(); } /* DEFINES_METHOD — Class -> Method when the method's parent_class resolves. */ TEST(contract_edge_defines_method) { static const LangFile f[] = {{"greeter.py", "class Greeter:\n def hello(self):\n return \"hi\"\n\n" " def bye(self):\n return \"bye\"\n\n\n" "def main():\n g = Greeter()\n return g.hello()\n"}}; ASSERT_TRUE(edge_present(f, 1, "DEFINES_METHOD", 1)); /* Greeter.hello, Greeter.bye */ PASS(); } /* CONTAINS_FILE — Folder/Project -> File for each discovered file. */ TEST(contract_edge_contains_file) { static const LangFile f[] = { {"pkg/util.py", "def add(a, b):\n return a + b\n\n\ndef sub(a, b):\n return a - b\n"}, {"pkg/helpers.py", "def greet(name):\n return \"hello \" + name\n\n\n" "class Greeter:\n def __init__(self, prefix):\n" " self.prefix = prefix\n\n" " def say(self, name):\n return self.prefix + name\n"}}; ASSERT_TRUE(edge_present(f, 2, "CONTAINS_FILE", 1)); /* pkg/ -> util.py, helpers.py */ PASS(); } /* INHERITS — subclass -> base via base_classes resolving to a Class/Type node. * Uses JAVA `extends`: Java's base_classes extraction is verified by * test_extraction.c:java_class_extends_and_implements, and same-file classes * resolve same-module. (Python `class X(Base)` does NOT yet work in-unit — that * is a real extraction bug reproduced RED by * test_extraction.c:python_class_base_extracted_bare; do not use Python here.) */ TEST(contract_edge_inherits) { static const LangFile f[] = { {"Zoo.java", "class Base {\n int base() { return 0; }\n}\n\n" "class Animal extends Base {\n int speak() { return 1; }\n}\n\n" "class Dog extends Animal {\n int speak() { return 2; }\n}\n"}}; ASSERT_TRUE(edge_present(f, 1, "INHERITS", 1)); /* Animal->Base, Dog->Animal */ PASS(); } /* IMPLEMENTS — Rust `impl Trait for Struct` (Java/TS `implements` -> INHERITS). */ TEST(contract_edge_implements) { static const LangFile f[] = {{"shapes.rs", "trait Greet {\n fn hello(&self) -> String;\n}\n\n" "trait Area {\n fn area(&self) -> f64;\n}\n\n" "struct English;\n\nstruct Square {\n side: f64,\n}\n\n" "impl Greet for English {\n fn hello(&self) -> String {\n" " String::from(\"hi\")\n }\n}\n\n" "impl Area for Square {\n fn area(&self) -> f64 {\n" " self.side * self.side\n }\n}\n\n" "impl Greet for Square {\n fn hello(&self) -> String {\n" " String::from(\"square\")\n }\n}\n"}}; ASSERT_TRUE(edge_present(f, 1, "IMPLEMENTS", 2)); /* 3 impl-for blocks */ PASS(); } /* DECORATES — decorated def -> locally-defined decorator function. */ TEST(contract_edge_decorates) { static const LangFile f[] = {{"audit.py", "def audit(func):\n def wrapper(*args, **kwargs):\n" " return func(*args, **kwargs)\n return wrapper\n\n\n" "def trace(func):\n def inner(*args, **kwargs):\n" " return func(*args, **kwargs)\n return inner\n\n\n" "@audit\ndef process_order(order_id):\n return order_id\n\n\n" "@trace\ndef cancel_order(order_id):\n return order_id\n\n\n" "@audit\ndef refund_order(order_id):\n return order_id\n"}}; ASSERT_TRUE(edge_present(f, 1, "DECORATES", 2)); /* 3 decorated defs */ PASS(); } /* TESTS_FILE — test file -> production file via path derivation (test_foo->foo). */ TEST(contract_edge_tests_file) { static const LangFile f[] = { {"foo.py", "def add(a, b):\n return a + b\n\n\ndef multiply(a, b):\n return a * b\n"}, {"test_foo.py", "from foo import add, multiply\n\n\n" "def test_add():\n assert add(2, 3) == 5\n\n\n" "def test_multiply():\n assert multiply(2, 3) == 6\n"}}; ASSERT_TRUE(edge_present(f, 2, "TESTS_FILE", 1)); PASS(); } /* HANDLES — Flask @app.route decorator handler -> Route node. */ TEST(contract_edge_handles) { static const LangFile f[] = { {"app.py", "from flask import Flask\n\napp = Flask(__name__)\n\n\n" "@app.route(\"/users\")\ndef list_users():\n return {\"users\": []}\n\n\n" "@app.route(\"/health\")\ndef health_check():\n return {\"status\": \"ok\"}\n"}}; ASSERT_TRUE(edge_present(f, 1, "HANDLES", 1)); /* 2 route handlers */ PASS(); } /* HTTP_CALLS — call resolving to a QN containing an HTTP-client lib id * ("requests") with a URL-shaped first arg -> Route node. Uses the SEQUENTIAL * pass path (small fixture): a local wrapper whose QN carries the lib substring. */ TEST(contract_edge_http_calls) { static const LangFile f[] = { {"service_b.py", "def requests_get(url, params=None):\n return {\"url\": url, \"params\": params}\n\n\n" "def requests_post(url, body=None):\n return {\"url\": url, \"body\": body}\n\n\n" "def service_a_fetch_order(order_id):\n" " return requests_get(\"/api/orders/list\", params={\"id\": order_id})\n\n\n" "def service_a_create_order(payload):\n" " return requests_post(\"/api/orders/create\", body=payload)\n"}}; ASSERT_TRUE(edge_present(f, 1, "HTTP_CALLS", 1)); PASS(); } /* ASYNC_CALLS — call resolving to a QN containing an async-broker id (dir * "pubsub/" injects it via the FQN scheme) with a topic-shaped first arg. */ TEST(contract_edge_async_calls) { static const LangFile f[] = { {"pubsub/publisher.py", "def publish_event(topic, payload):\n return (topic, payload)\n\n\n" "def enqueue_order(order):\n return publish_event(\"order-events-topic\", order)\n\n\n" "def enqueue_shipment(shipment):\n" " return publish_event(\"shipment-events-topic\", shipment)\n"}}; ASSERT_TRUE(edge_present(f, 1, "ASYNC_CALLS", 1)); PASS(); } /* DATA_FLOWS — caller -> handler through a shared Route: a decorator handler * (HANDLES) and an HTTP call to the SAME ANY-method path (HTTP_CALLS) on a * Route whose QN matches (__route__ANY__/orders). create_data_flows then links * the HTTP caller to the route handler. Sequential-path-safe: the HTTP call * resolves to a local "requests"-substring wrapper, and method NULL -> "ANY". */ TEST(contract_edge_data_flows) { static const LangFile f[] = { {"app.py", "from flask import Flask\n\napp = Flask(__name__)\n\n\n" "@app.route(\"/orders\")\ndef list_orders():\n return {\"orders\": []}\n\n\n" "def requests_get(url, params=None):\n return {\"url\": url}\n\n\n" "def client():\n return requests_get(\"/orders\")\n"}}; ASSERT_TRUE(edge_present(f, 1, "DATA_FLOWS", 1)); /* client -> list_orders via /orders */ PASS(); } /* SIMILAR_TO — structural near-clones (>=0.95 MinHash Jaccard), same extension. * This is the exact clone pair from tests/test_simhash.c. */ TEST(contract_edge_similar_to) { static const LangFile f[] = { {"pkg/validation/user_validator.go", "package validation\nimport \"errors\"\nimport \"strings\"\n" "func ValidateUser(u User) error {\n" " if u.Name == \"\" { return errors.New(\"name required\") }\n" " if len(u.Name) > 100 { return errors.New(\"name too long\") }\n" " if u.Age < 0 { return errors.New(\"invalid age\") }\n" " if u.Age > 200 { return errors.New(\"age too high\") }\n" " if u.Email == \"\" { return errors.New(\"email required\") }\n" " if !strings.Contains(u.Email, \"@\") { return errors.New(\"invalid email\") }\n" " if u.Phone == \"\" { return errors.New(\"phone required\") }\n" " if len(u.Phone) < 7 { return errors.New(\"phone too short\") }\n" " if u.Country == \"\" { return errors.New(\"country required\") }\n" " for _, c := range u.Tags {\n if c == \"\" { return errors.New(\"empty tag\") " "}\n }\n" " return nil\n}\n"}, {"pkg/validation/order_validator.go", "package validation\nimport \"errors\"\nimport \"strings\"\n" "func ValidateOrder(o Order) error {\n" " if o.Title == \"\" { return errors.New(\"title required\") }\n" " if len(o.Title) > 100 { return errors.New(\"title too long\") }\n" " if o.Amount < 0 { return errors.New(\"invalid amount\") }\n" " if o.Amount > 200 { return errors.New(\"amount too high\") }\n" " if o.Status == \"\" { return errors.New(\"status required\") }\n" " if !strings.Contains(o.Status, \"@\") { return errors.New(\"invalid status\") }\n" " if o.Region == \"\" { return errors.New(\"region required\") }\n" " if len(o.Region) < 7 { return errors.New(\"region too short\") }\n" " if o.Vendor == \"\" { return errors.New(\"vendor required\") }\n" " for _, c := range o.Items {\n if c == \"\" { return errors.New(\"empty " "item\") }\n }\n" " return nil\n}\n"}}; ASSERT_TRUE(edge_present(f, 2, "SIMILAR_TO", 1)); PASS(); } /* SEMANTICALLY_RELATED — near-identical functions sharing vocabulary/callees/ * types but DIFFERENT control-flow shapes, so the semantic score clears 0.75 * while the structural MinHash stays under the 0.95 SIMILAR_TO short-circuit. */ TEST(contract_edge_semantically_related) { static const LangFile f[] = { {"records.py", "def sanitize(value: str) -> str:\n return value.strip().lower()\n\n\n" "def lookup(table: dict, key: str) -> str:\n return table.get(key, \"\")\n\n\n" "def audit_log(message: str) -> None:\n print(message)\n\n\n" "def normalize_user_record(record: dict, table: dict) -> dict:\n" " \"\"\"Normalize a user record by sanitizing fields and looking up defaults.\"\"\"\n" " result = {}\n name = sanitize(record.get(\"name\", \"\"))\n" " email = sanitize(record.get(\"email\", \"\"))\n role = lookup(table, name)\n" " if name and email:\n result[\"name\"] = name\n result[\"email\"] = " "email\n" " result[\"role\"] = role\n audit_log(\"normalized user record\")\n" " return result\n\n\n" "def normalize_account_record(record: dict, table: dict) -> dict:\n" " \"\"\"Normalize an account record by sanitizing fields and looking up " "defaults.\"\"\"\n" " result = {}\n name = sanitize(record.get(\"name\", \"\"))\n" " email = sanitize(record.get(\"email\", \"\"))\n role = lookup(table, name)\n" " while name and email:\n result[\"name\"] = name\n result[\"email\"] = " "email\n" " result[\"role\"] = role\n audit_log(\"normalized account record\")\n" " break\n return result\n\n\n" "def normalize_member_record(record: dict, table: dict) -> dict:\n" " \"\"\"Normalize a member record by sanitizing fields and looking up defaults.\"\"\"\n" " result = {}\n name = sanitize(record.get(\"name\", \"\"))\n" " email = sanitize(record.get(\"email\", \"\"))\n role = lookup(table, name)\n" " for _ in range(1):\n if not (name and email):\n continue\n" " result[\"name\"] = name\n result[\"email\"] = email\n" " result[\"role\"] = role\n audit_log(\"normalized member record\")\n" " return result\n\n\n" "def normalize_profile_record(record: dict, table: dict) -> dict:\n" " \"\"\"Normalize a profile record by sanitizing fields and looking up " "defaults.\"\"\"\n" " result = {}\n name = sanitize(record.get(\"name\", \"\"))\n" " email = sanitize(record.get(\"email\", \"\"))\n role = lookup(table, name)\n" " try:\n assert name and email\n result[\"name\"] = name\n" " result[\"email\"] = email\n result[\"role\"] = role\n" " audit_log(\"normalized profile record\")\n" " except AssertionError:\n audit_log(\"skipped profile record\")\n" " return result\n"}}; ASSERT_TRUE(edge_present(f, 1, "SEMANTICALLY_RELATED", 1)); PASS(); } /* ── P6 (continued): the remaining pipeline edge types ───────────────── * TESTS + DEPENDS_ON are produced in any pipeline path. The service-protocol * edges (GRAPHQL_CALLS/GRPC_CALLS/TRPC_CALLS) and INFRA_MAPS are emitted ONLY * in the PARALLEL path (file_count > 50), and FILE_CHANGES_WITH needs real git * history — so those use a padded / git-initialised fixture. All fixtures here * were empirically verified to produce their edge against the prod binary. */ /* TESTS — function-level test->tested-function (distinct from TESTS_FILE). * Requires a CROSS-FILE call: a Test* function in a *_test file calling a * production function in a NON-test file (the target must not be in a test * path). pass_tests runs regardless of file count. */ TEST(contract_edge_tests) { static const LangFile f[] = { {"calc.go", "package calc\n\nfunc Add(a int, b int) int {\n\treturn a + b\n}\n\n" "func Mul(a int, b int) int {\n\treturn a * b\n}\n"}, {"calc_test.go", "package calc\n\nfunc TestAdd(t *T) {\n\tgot := Add(2, 3)\n\t_ = got\n}\n\n" "func TestMul(t *T) {\n\tgot := Mul(2, 3)\n\t_ = got\n}\n"}}; ASSERT_TRUE(edge_present(f, 2, "TESTS", 1)); /* TestAdd->Add, TestMul->Mul */ PASS(); } /* #408: package.json `workspaces` cross-package IMPORTS produce zero edges * (v0.7.0 follow-up to #308, which fixed tsconfig `paths`). A Lerna/Yarn-style * monorepo where packages/b imports a sibling by its package name (@org/a) should * resolve via the sibling's package.json `name` -> dir (pass_pkgmap.c) into a * cross-package IMPORTS edge. Today bare-specifier workspace imports yield zero. * RED until workspace `@org/pkg` resolution produces the edge. */ TEST(contract_edge_workspaces_imports_issue408) { static const LangFile f[] = { {"package.json", "{\"name\":\"root\",\"private\":true,\"workspaces\":[\"packages/*\"]}\n"}, {"packages/a/package.json", "{\"name\":\"@org/a\",\"version\":\"1.0.0\",\"main\":\"index.js\"}\n"}, {"packages/a/index.js", "export function fromA() {\n return 1;\n}\n"}, {"packages/b/package.json", "{\"name\":\"@org/b\",\"version\":\"1.0.0\",\"main\":\"index.js\"," "\"dependencies\":{\"@org/a\":\"1.0.0\"}}\n"}, {"packages/b/index.js", "import { fromA } from '@org/a';\n\nexport function useA() {\n return fromA();\n}\n"}}; ASSERT_TRUE(edge_present(f, 5, "IMPORTS", 1)); PASS(); } /* #767: a wildcard tsconfig alias for the "@lib" prefix (mapped to ./src/lib) * shares that prefix with an unrelated scoped npm package ("@lib/external-pkg", * meant to resolve normally from node_modules). The engine has no such file * and must NOT invent an edge to the "src/lib" Folder node via a later * fallback strategy that re-tries the truncated "@lib" prefix against the * tsconfig's other, bare alias entry. Zero IMPORTS edges in the whole project * is the correct outcome: the same as any other unresolved external import. */ TEST(contract_edge_imports_alias_no_phantom_folder_edge_issue767) { LangProj lp; static const LangFile f[] = { {"tsconfig.json", "{\n \"compilerOptions\": {\n \"paths\": {\n" " \"@lib\": [\"./src/lib\"],\n" " \"@lib/*\": [\"./src/lib/*\"]\n }\n }\n}\n"}, {"src/lib/thing.ts", "export const Thing = {};\n"}, {"src/consumer.ts", "import { ClientC } from '@lib/external-pkg';\n\n" "export function useClient() {\n return new ClientC();\n}\n"}}; cbm_store_t *store = lang_index_files(&lp, f, 3); int got = store ? cbm_store_count_edges_by_type(store, lp.project, "IMPORTS") : -1; if (got != 0) { fprintf(stderr, " [EDGE] FAIL IMPORTS count=%d expected=0 (phantom Folder edge)\n", got); } ASSERT_EQ(got, 0); lang_cleanup(&lp, store); PASS(); } /* #767 regression guard: a wildcard tsconfig alias resolving to a REAL, * indexed file must still produce its IMPORTS edge — the import-targetable * label filter must reject Folder/Project/etc. matches without rejecting * legitimate File/Module matches. */ TEST(contract_edge_imports_alias_resolves_real_file_issue767) { static const LangFile f[] = { {"tsconfig.json", "{\n \"compilerOptions\": {\n \"paths\": {\n" " \"@lib\": [\"./src/lib\"],\n" " \"@lib/*\": [\"./src/lib/*\"]\n }\n }\n}\n"}, {"src/lib/thing.ts", "export const Thing = {};\n"}, {"src/consumer.ts", "import { Thing } from '@lib/thing';\n\n" "export function useThing() {\n return Thing;\n}\n"}}; ASSERT_TRUE(edge_present(f, 3, "IMPORTS", 1)); PASS(); } /* True if some CALLS edge's TARGET node carries `label` and a QN ending with * `qn_suffix` — i.e. the call resolved to that specific definition. */ static int calls_edge_targets(cbm_store_t *store, const char *project, const char *label, const char *qn_suffix) { cbm_edge_t *edges = NULL; int n = 0; if (cbm_store_find_edges_by_type(store, project, "CALLS", &edges, &n) != CBM_STORE_OK) return 0; int found = 0; size_t sl = strlen(qn_suffix); for (int i = 0; i < n && !found; i++) { cbm_node_t tgt; if (cbm_store_find_node_by_id(store, edges[i].target_id, &tgt) != CBM_STORE_OK) continue; const char *qn = tgt.qualified_name; if (qn && tgt.label && strcmp(tgt.label, label) == 0) { size_t ql = strlen(qn); if (ql >= sl && strcmp(qn + ql - sl, qn_suffix) == 0) found = 1; } cbm_node_free_fields(&tgt); } cbm_store_free_edges(edges, n); return found; } /* #871: a CommonJS require() binding shadowed call resolution. `const doThing * = require("../bs/doThing")` emitted a Variable def for `doThing` in the * importing module, so the call `doThing(...)` resolved to that same-module * Variable (an import ALIAS, not a definition) instead of following the * recorded import to the exported function — which stayed at zero inbound * CALLS (the reporter's disconnected-node symptom). ESM `import` bindings * never materialize as Variables; the identifier-bound require form must * behave identically: no shadowing Variable, CALLS edge lands on the real * Function. Fixture = the reporter's exact two-file repro. */ TEST(contract_edge_commonjs_require_call_resolves_issue871) { LangProj lp; static const LangFile f[] = { {"src/bs/doThing.js", "module.exports = async function doThing({ id }) {\n return id;\n};\n"}, {"src/mutations/doThing.js", "const doThing = require(\"../bs/doThing\");\n\n" "module.exports = async (parent, args) => {\n return doThing({ id: args.id });\n};\n"}}; cbm_store_t *store = lang_index_files(&lp, f, 2); ASSERT_TRUE(store != NULL); /* The call resolves THROUGH the require to the exported function. */ int resolved = calls_edge_targets(store, lp.project, "Function", ".bs.doThing.doThing"); /* The alias must not swallow the call: no CALLS edge may terminate on a * Variable in the importing module (ESM parity: the binding is an import, * not a definition). */ int shadowed = calls_edge_targets(store, lp.project, "Variable", ".mutations.doThing.doThing"); if (!resolved || shadowed) { fprintf(stderr, " [871] FAIL resolved=%d shadowed=%d (require binding must resolve to " "the exported Function, not the local alias Variable)\n", resolved, shadowed); } ASSERT_TRUE(resolved); ASSERT_TRUE(!shadowed); lang_cleanup(&lp, store); PASS(); } /* DEPENDS_ON — Helm Chart.yaml `dependencies:` -> per-dependency Chart node. * Basename must be exactly "Chart.yaml"; pass_k8s runs in both pipeline paths. */ TEST(contract_edge_depends_on) { static const LangFile f[] = { {"charts/myapp/Chart.yaml", "apiVersion: v2\nname: myapp\nversion: 1.0.0\ndescription: A test chart\n" "dependencies:\n - name: postgresql\n version: \"11.6.12\"\n" " repository: \"https://charts.bitnami.com/bitnami\"\n" " - name: redis\n version: \"16.8.5\"\n" " repository: \"https://charts.bitnami.com/bitnami\"\n"}, {"main.py", "def hello():\n return \"world\"\n"}}; ASSERT_TRUE(edge_present(f, 2, "DEPENDS_ON", 1)); /* postgresql, redis */ PASS(); } /* Index `meaningful` files plus enough trivial pad files to cross the * MIN_FILES_FOR_PARALLEL (=50) threshold, forcing the PARALLEL pipeline path * (the only path that emits GRAPHQL_CALLS/GRPC_CALLS/TRPC_CALLS/INFRA_MAPS). */ enum { PARALLEL_PAD_FILES = 52 }; static cbm_store_t *index_parallel_fixture(LangProj *lp, const LangFile *meaningful, int n_mean) { static char pad_name[PARALLEL_PAD_FILES][40]; static char pad_body[PARALLEL_PAD_FILES][64]; LangFile files[PARALLEL_PAD_FILES + 16] = {0}; /* zero-init: GCC -Werror=maybe-uninitialized */ int n = 0; for (int i = 0; i < n_mean; i++) { files[n++] = meaningful[i]; } for (int i = 0; i < PARALLEL_PAD_FILES; i++) { snprintf(pad_name[i], sizeof(pad_name[i]), "pad/pad_%02d.py", i); snprintf(pad_body[i], sizeof(pad_body[i]), "def pad_%02d():\n return %d\n", i, i); files[n].name = pad_name[i]; files[n].content = pad_body[i]; n++; } return lang_index_files(lp, files, n); } /* GRAPHQL_CALLS + GRPC_CALLS + TRPC_CALLS + INFRA_MAPS — all parallel-path-only, * so they share one padded (>50-file) index. Each edge is triggered by an * independent in-repo symbol whose resolved QN carries the relevant library * substring; combining them in one repo doesn't couple them. */ TEST(contract_edge_parallel_service_edges) { static const LangFile meaningful[] = { /* GRAPHQL_CALLS: local gql() — resolved callee QN contains "gql". */ {"gql.py", "def gql(query_string):\n return query_string\n"}, {"client.py", "from gql import gql\n\n\ndef fetch_user():\n" " return gql(\"query GetUser { user { id name } }\")\n\n\n" "def create_user():\n" " return gql(\"mutation CreateUser { addUser(name: \\\"x\\\") { id } }\")\n"}, /* TRPC_CALLS: local createTRPCProxyClient (same-module resolution). */ {"trpc_client.ts", "export function createTRPCProxyClient(opts: any): any {\n return { invoke: () => opts " "};\n}\n\n" "export function loadUser(id: string) {\n const client = createTRPCProxyClient({ id });\n" " return client;\n}\n"}, /* GRPC_CALLS: Go ServiceClient suffix heuristic (cross-package call). */ {"go.mod", "module example.com/grpcdemo\n\ngo 1.21\n"}, {"cartpb/cart_grpc.pb.go", "package cartpb\n\nimport \"context\"\n\ntype GetCartRequest struct{ UserId string }\n" "type GetCartResponse struct{ Items int }\n\ntype CartServiceClient interface {\n" "\tGetCart(ctx context.Context, in *GetCartRequest) (*GetCartResponse, error)\n}\n\n" "type cartServiceClient struct{}\n\nfunc NewCartServiceClient(cc interface{}) " "CartServiceClient {\n" "\treturn &cartServiceClient{}\n}\n\nfunc (c *cartServiceClient) GetCart(ctx " "context.Context, " "in *GetCartRequest) (*GetCartResponse, error) {\n\treturn &GetCartResponse{}, nil\n}\n"}, {"client/main.go", "package main\n\nimport (\n\t\"context\"\n\n\tpb \"example.com/grpcdemo/cartpb\"\n)\n\n" "func FetchCart(conn interface{}) {\n\tclient := pb.NewCartServiceClient(conn)\n" "\tclient.GetCart(context.Background(), &pb.GetCartRequest{UserId: \"u1\"})\n}\n\n" "func main() {\n\tFetchCart(nil)\n}\n"}, /* INFRA_MAPS: ASYNC_CALLS topic route (pubsub) matched to an infra YAML * subscription whose push_endpoint becomes the infra route. */ {"app/pubsub.py", "def publish(topic, data):\n return (topic, data)\n"}, {"app/publisher.py", "import pubsub\n\n\ndef publish_order(data):\n" " pubsub.publish(\"order-events\", data)\n"}, {"infra/pubsub/subscription.yaml", "subscriptions:\n - name: order-worker-sub\n topic: order-events\n" " config:\n push_endpoint: https://order-worker-abc123-uc.a.run.app/handle\n"}}; LangProj lp; cbm_store_t *store = index_parallel_fixture(&lp, meaningful, (int)(sizeof(meaningful) / sizeof(meaningful[0]))); int graphql = store ? cbm_store_count_edges_by_type(store, lp.project, "GRAPHQL_CALLS") : -1; int grpc = store ? cbm_store_count_edges_by_type(store, lp.project, "GRPC_CALLS") : -1; int trpc = store ? cbm_store_count_edges_by_type(store, lp.project, "TRPC_CALLS") : -1; int infra = store ? cbm_store_count_edges_by_type(store, lp.project, "INFRA_MAPS") : -1; if (graphql < 1 || grpc < 1 || trpc < 1 || infra < 1) { fprintf(stderr, " [EDGE] parallel-service: GRAPHQL_CALLS=%d GRPC_CALLS=%d TRPC_CALLS=%d " "INFRA_MAPS=%d\n", graphql, grpc, trpc, infra); dump_edge_histogram(store, lp.project); } lang_cleanup(&lp, store); ASSERT_TRUE(graphql >= 1); ASSERT_TRUE(grpc >= 1); ASSERT_TRUE(trpc >= 1); ASSERT_TRUE(infra >= 1); PASS(); } /* Run "git -C "; direct git invocation (no shell builtins) so it * works under both POSIX shells and cmd.exe. Returns the command exit status. */ static int run_git(const char *dir, const char *args) { char cmd[1024]; snprintf(cmd, sizeof(cmd), "git -C \"%s\" %s", dir, args); return system(cmd); } /* FILE_CHANGES_WITH — git co-change coupling: two files that change together * across >=3 commits (coupling >= 0.3). The standard harness indexes a plain * non-git dir, so this test builds its own git repo (init + 4 commits touching * BOTH files) before indexing via lang_open_indexed. */ TEST(contract_edge_file_changes_with) { LangProj lp; memset(&lp, 0, sizeof(lp)); snprintf(lp.tmpdir, sizeof(lp.tmpdir), "/tmp/cbm_fcw_XXXXXX"); ASSERT_NOT_NULL(cbm_mkdtemp(lp.tmpdir)); lc_to_fwd_slashes(lp.tmpdir); char a[700]; char b[700]; snprintf(a, sizeof(a), "%s/alpha.py", lp.tmpdir); snprintf(b, sizeof(b), "%s/beta.py", lp.tmpdir); th_write_file(a, "def alpha_v1():\n return 1\n"); th_write_file(b, "def beta_v1():\n return 1\n"); /* git must be present (this IS a git project) — a failed init means a broken * environment, which is a real failure, not a skip. git is installed on every * CI platform (incl. the Windows msys2 env), and run_git uses `git -C` with no * POSIX-shell syntax, so this runs everywhere. */ ASSERT_EQ(run_git(lp.tmpdir, "init -q"), 0); run_git(lp.tmpdir, "config user.email t@t.io"); run_git(lp.tmpdir, "config user.name t"); run_git(lp.tmpdir, "add -A"); run_git(lp.tmpdir, "commit -qm c1"); for (int i = 2; i <= 4; i++) { th_append_file(a, "\n\ndef alpha_more():\n return 9\n"); th_append_file(b, "\n\ndef beta_more():\n return 9\n"); char msg[24]; run_git(lp.tmpdir, "add -A"); snprintf(msg, sizeof(msg), "commit -qm c%d", i); run_git(lp.tmpdir, msg); } cbm_store_t *store = lang_open_indexed(&lp); int fcw = store ? cbm_store_count_edges_by_type(store, lp.project, "FILE_CHANGES_WITH") : -1; if (fcw < 1) { dump_edge_histogram(store, lp.project); } lang_cleanup(&lp, store); ASSERT_TRUE(fcw >= 1); /* alpha.py <-> beta.py co-change across 4 commits */ PASS(); } /* ══════════════════════════════════════════════════════════════════ */ SUITE(lang_contract) { /* The three known-bug reproductions. Kotlin reproduces in-fixture (an * extraction-layer bug, scale-independent). C call-attribution and the * Java/TS extraction crash only manifest at real-repo scale, so a small * fixture passes here — their reproductions live in the real-repo scale * tier; these fast contracts still guard against regressions. */ RUN_TEST(contract_kotlin_imports_extracted); RUN_TEST(contract_c_calls_attributed_to_function); RUN_TEST(contract_java_extract_no_crash); /* Rich per-language invariants (P3). */ RUN_TEST(contract_go_calls); RUN_TEST(contract_rust_methods); RUN_TEST(contract_csharp_methods); RUN_TEST(contract_php_methods); RUN_TEST(contract_java_methods); RUN_TEST(contract_kotlin_methods); RUN_TEST(contract_python_relative_import); RUN_TEST(contract_typescript_relative_import); /* Graph-level breadth across all grammars (P4). */ RUN_TEST(contract_all_grammars_in_graph); /* CALLS-edge breadth across non-LSP languages (P5). */ RUN_TEST(contract_calls_breadth); /* Cross-cutting / semantic edge-type presence (P6). Each asserts the * pipeline still emits that edge type; a RED here is a real regression. */ RUN_TEST(contract_edge_defines); RUN_TEST(contract_edge_defines_method); RUN_TEST(contract_edge_contains_file); RUN_TEST(contract_edge_inherits); RUN_TEST(contract_edge_implements); RUN_TEST(contract_edge_decorates); RUN_TEST(contract_edge_tests_file); RUN_TEST(contract_edge_handles); RUN_TEST(contract_edge_http_calls); RUN_TEST(contract_edge_async_calls); RUN_TEST(contract_edge_data_flows); RUN_TEST(contract_edge_similar_to); RUN_TEST(contract_edge_semantically_related); /* P6 (continued): remaining edge types — TESTS, DEPENDS_ON, the * parallel-path service edges (GRAPHQL/GRPC/TRPC_CALLS + INFRA_MAPS), and * FILE_CHANGES_WITH (git co-change). Completes 25-edge-type coverage. */ RUN_TEST(contract_edge_tests); RUN_TEST(contract_edge_workspaces_imports_issue408); RUN_TEST(contract_edge_imports_alias_no_phantom_folder_edge_issue767); RUN_TEST(contract_edge_imports_alias_resolves_real_file_issue767); RUN_TEST(contract_edge_commonjs_require_call_resolves_issue871); RUN_TEST(contract_edge_depends_on); RUN_TEST(contract_edge_parallel_service_edges); RUN_TEST(contract_edge_file_changes_with); }