Add per-grammar regression suite across all 159 grammars

Index a fixture for every grammar through the full pipeline and assert graph-level invariants: golden node-label histograms, def/node breadth, CALLS and IMPORTS breadth, presence of all 26 pipeline edge types (structural, type-relationship, service/dataflow, and similarity), no-crash, and call resolution. Reproduced quality gaps (under-extraction, import-extraction, and CALLS-resolution) are kept as hard failures for the fix phase rather than skipped. Adds a real-repo scale tier (scale_contract.sh) and ignores local-only private/ scratch.
This commit is contained in:
Martin Vogel committed 2026-06-05 21:53:25 +02:00
1 parent eed87fd372
commit 7a43b38ef7
9 files changed
+1937 -13

No files matched your search

+3
View File
@@ -41,6 +41,9 @@ Thumbs.db
memory/
reference/
# Local-only scratch / session notes (never pushed)
private/
# Build artifacts
build/
node_modules/
+3 -1
View File
@@ -276,6 +276,8 @@ TEST_FOUNDATION_SRCS = \
TEST_EXTRACTION_SRCS = \
tests/test_extraction.c \
tests/test_grammar_regression.c \
tests/test_grammar_labels.c \
tests/test_grammar_imports.c \
tests/test_ac.c
TEST_STORE_SRCS = \
@@ -338,7 +340,7 @@ TEST_JAVA_LSP_SRCS = tests/test_java_lsp.c tests/test_java_lsp_coverage.c
TEST_KOTLIN_LSP_SRCS = tests/test_kotlin_lsp.c
TEST_RUST_LSP_SRCS = tests/test_rust_lsp.c
TEST_INTEGRATION_SRCS = tests/test_integration.c tests/test_incremental.c
TEST_INTEGRATION_SRCS = tests/test_integration.c tests/test_incremental.c tests/test_lang_contract.c
TEST_TRACES_SRCS = tests/test_traces.c
+31
View File
@@ -0,0 +1,31 @@
/*
* grammar_cases.h — Shared per-language fixture table.
*
* One minimal source fixture per supported grammar. Shared by:
* - test_grammar_regression.c : EXTRACTION-level breadth (cbm_extract_file with
* an explicit language → defs/labels). Bypasses discover, so it covers every
* grammar including those whose file extension collides with another's.
* - test_lang_contract.c : GRAPH-level breadth (full pipeline index → the
* fixture's defs must reach the graph as nodes).
*
* Keep this the single source of truth for per-grammar fixtures.
*/
#ifndef CBM_TEST_GRAMMAR_CASES_H
#define CBM_TEST_GRAMMAR_CASES_H
#include "cbm.h"
#include <stddef.h>
typedef struct {
const char *name; /* short grammar label (unique) */
CBMLanguage lang; /* explicit language for extraction-level tests */
const char *path; /* representative filename (drives discover by ext/name) */
const char *src; /* fixture source */
int min_defs; /* catastrophic-break floor (0 for data/config/markup) */
const char *expect[4]; /* expected def names (NULL-terminated), optional */
} GrammarCase;
extern const GrammarCase CBM_GRAMMAR_CASES[];
extern const size_t CBM_GRAMMAR_CASES_COUNT;
#endif /* CBM_TEST_GRAMMAR_CASES_H */
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env bash
# scale_contract.sh — Real-repo SCALE-TIER contracts for known-bug languages.
#
# Some behavioral bugs only manifest at real-repo scale and cannot be reproduced
# by the small in-process fixtures in tests/test_lang_contract.c:
# - Java / TypeScript : SIGBUS during parallel extraction on large repos.
# - C : function-call attribution (calls land on the file's
# Module node instead of the enclosing Function).
# - Kotlin : 0 IMPORTS edges (package -> module resolution).
#
# This tier runs the COMPILED binary on the cbm-bench-validate repos and asserts
# invariants at scale. It is SLOW and needs the local bench repos, so it is
# OPT-IN — not part of the fast `scripts/test.sh` unit run.
#
# Usage:
# bash tests/scale_contract.sh [lang ...] # default: kotlin java ts
# CBM_SCALE_INCLUDE_C=1 bash tests/scale_contract.sh c # add the slow C tier
# CBM_BENCH_DIR=/path bash tests/scale_contract.sh # override repo root
# Exit 0 if all selected contracts pass, non-zero otherwise.
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BIN="$ROOT/build/c/codebase-memory-mcp"
BENCH="${CBM_BENCH_DIR:-$HOME/cbm-bench-validate}"
PY="${PYTHON:-python3.9}"
FAILURES=0
RAN=0
# ── repo map (lang -> bench subdir) ──────────────────────────────
repo_for() {
case "$1" in
kotlin) echo "$BENCH/ktorio_ktor" ;;
java) echo "$BENCH/spring-projects_spring-boot" ;;
ts) echo "$BENCH/microsoft_TypeScript" ;;
c) echo "$BENCH/linux_mono/copy1" ;;
*) echo "" ;;
esac
}
# project name == cbm_project_name_from_path(): strip leading '/', map every
# char outside [A-Za-z0-9._-] to '-'.
project_name() { printf '%s' "$1" | sed 's#^/##; s#[^A-Za-z0-9._-]#-#g'; }
# Run query_graph and print the first integer in the JSON response (or -1).
query_count() {
local proj="$1" cypher="$2" resp
resp="$("$BIN" cli query_graph "{\"project\":\"$proj\",\"query\":\"$cypher\"}" 2>/dev/null)"
printf '%s' "$resp" | "$PY" -c 'import sys,re
s=sys.stdin.read()
m=re.findall(r"-?\d+", s)
print(m[0] if m else -1)' 2>/dev/null || echo -1
}
assert_lang() {
local lang="$1" repo
repo="$(repo_for "$lang")"
if [ -z "$repo" ] || [ ! -d "$repo" ]; then
echo "[scale] SKIP $lang — repo not found ($repo)"
return 0
fi
RAN=$((RAN + 1))
local proj
proj="$(project_name "$repo")"
rm -f "$HOME/.cache/codebase-memory-mcp/$proj.db"*
echo "[scale] $lang — indexing $repo ..."
"$BIN" cli index_repository "{\"repo_path\":\"$repo\"}" >/dev/null 2>&1
local rc=$?
# Contract 1 (all langs): indexing must not crash by signal (rc >= 128).
if [ "$rc" -ge 128 ]; then
echo "[scale] FAIL $lang — index crashed by signal (exit=$rc, signal=$((rc - 128)))"
FAILURES=$((FAILURES + 1))
return 0
fi
echo "[scale] ok $lang — index did not crash (exit=$rc)"
# Contract 2 (per-lang invariant).
case "$lang" in
kotlin)
local imports
imports="$(query_count "$proj" "MATCH ()-[r:IMPORTS]->() RETURN count(r) AS n")"
if [ "$imports" -ge 1 ] 2>/dev/null; then
echo "[scale] PASS kotlin — IMPORTS edges present (n>=$imports)"
else
echo "[scale] FAIL kotlin — 0 IMPORTS edges (got '$imports')"
FAILURES=$((FAILURES + 1))
fi
;;
c)
# Function nodes that have >=1 outbound CALLS (attribution invariant).
local fcalls
fcalls="$(query_count "$proj" "MATCH (a:Function)-[:CALLS]->() RETURN count(DISTINCT a) AS n")"
if [ "$fcalls" -ge 1 ] 2>/dev/null; then
echo "[scale] PASS c — Function-sourced CALLS present (functions-with-calls>=$fcalls)"
else
echo "[scale] FAIL c — no Function node has outbound CALLS (got '$fcalls')"
FAILURES=$((FAILURES + 1))
fi
;;
java | ts)
echo "[scale] PASS $lang — no-crash contract satisfied"
;;
esac
}
# ── build the prod binary if missing ─────────────────────────────
if [ ! -x "$BIN" ]; then
echo "[scale] building prod binary ..."
(cd "$ROOT" && ./scripts/build.sh) || { echo "[scale] FAIL — build failed"; exit 1; }
fi
[ -x "$BIN" ] || { echo "[scale] FAIL — binary not found at $BIN"; exit 1; }
# ── select languages ─────────────────────────────────────────────
LANGS=("$@")
if [ "${#LANGS[@]}" -eq 0 ]; then
LANGS=(kotlin java ts)
[ "${CBM_SCALE_INCLUDE_C:-0}" = "1" ] && LANGS+=(c)
fi
for lang in "${LANGS[@]}"; do
assert_lang "$lang"
done
echo "────────────────────────────────────────────"
if [ "$FAILURES" -eq 0 ]; then
echo "[scale] All scale contracts passed ($RAN ran)"
exit 0
fi
echo "[scale] $FAILURES scale contract(s) FAILED ($RAN ran)"
exit 1
+152
View File
@@ -0,0 +1,152 @@
/*
* test_grammar_imports.c — Per-grammar IMPORTS EXTRACTION breadth.
*
* For every grammar cbm_extract_imports() handles, assert cbm_extract_file()
* captures its import/include/use/require statements (imports.count >= N).
* This is the EXTRACTION-level import contract: it uses an explicit language
* (no discover, no pkgmap), so it is reliable in-unit for ALL import-capable
* grammars — the graph IMPORTS-edge (which needs module resolution) is covered
* for relative imports by test_lang_contract.c and at real scale by the scale
* tier. No skips: a grammar that fails to extract its imports is a hard FAILURE
* reproducing the bug (gap_note documents the already-root-caused ones).
*/
#include "test_framework.h"
#include "cbm.h"
#include <string.h>
typedef struct {
const char *name;
CBMLanguage lang;
const char *filename;
const char *content;
int expected; /* imports.count must be >= this */
const char *gap_note;
} ImportCase;
static const ImportCase IMPORT_CASES[] = {
{"astro", CBM_LANG_ASTRO, "fixture.astro",
"<script>\nimport confetti from \"canvas-confetti\";\nimport dayjs from \"dayjs\";\n\nconfetti();\nconsole.log(dayjs().format());\n</script>\n<div>hi</div>\n",
2, NULL},
{"bash", CBM_LANG_BASH, "a.sh", "#!/usr/bin/env bash\nsource ./lib/util.sh\n. ./lib/other.sh\n", 2,
NULL},
{"c", CBM_LANG_C, "imports_fixture.c",
"#include <stdio.h>\n#include \"mymodule.h\"\n\nint main(void) {\n return 0;\n}\n", 2, NULL},
{"cpp", CBM_LANG_CPP, "main.cpp",
"#include <iostream>\n#include \"widget.h\"\n\nint main() {\n std::cout << \"hi\\n\";\n return 0;\n}\n",
2, NULL},
{"csharp", CBM_LANG_CSHARP, "Imports.cs",
"using System;\nusing static System.Math;\nusing Json = System.Text.Json;\n\nnamespace Demo;\n\nclass Program\n{\n static void Main() => Console.WriteLine(Sqrt(4.0));\n}\n",
3, NULL},
{"css", CBM_LANG_CSS, "styles.css",
"@import \"reset.css\";\n@import url(\"theme.css\");\n\nbody {\n margin: 0;\n}\n", 2, NULL},
{"dart", CBM_LANG_DART, "imports.dart",
"import 'dart:async';\nimport 'package:meta/meta.dart';\n\nvoid main() {}\n", 2,
"extract_imports.c SWIFT/DART case matches 'import_declaration' but tree-sitter-dart emits 'import_or_export' -> 0 imports"},
{"elixir", CBM_LANG_ELIXIR, "imports_fixture.ex",
"import Enum\nalias MyApp.Accounts.User\nrequire Logger\n", 3, NULL},
{"erlang", CBM_LANG_ERLANG, "greeter.erl",
"-module(greeter).\n-import(lists, [foldl/3]).\n-include_lib(\"kernel/include/file.hrl\").\n\nsum(Ns) -> foldl(fun(X, A) -> X + A end, 0, Ns).\n",
1, NULL},
{"form", CBM_LANG_FORM, "imports.frm", "#include \"procedures.prc\"\n#include \"tables.h\"\n", 2, NULL},
{"go", CBM_LANG_GO, "imports_fixture.go",
"package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\tmrand \"math/rand\"\n)\n\nfunc main() {\n\tfmt.Fprintln(os.Stdout, mrand.Int())\n}\n",
3, NULL},
{"groovy", CBM_LANG_GROOVY, "a.groovy",
"import java.util.List\nimport groovy.transform.CompileStatic\n\nclass Greeter {\n String greet() { \"hi\" }\n}\n",
2, NULL},
{"haskell", CBM_LANG_HASKELL, "a.hs",
"module Main where\n\nimport Data.List (sort)\nimport qualified Data.Map as Map\n\nmain :: IO ()\nmain = print (sort [3, 1, 2])\n",
2,
"imports nested under an 'imports' container node; parse_generic_imports scans only root children -> 0 (needs parse_haskell_imports, like Kotlin)"},
{"html", CBM_LANG_HTML, "fixture_imports.html",
"<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <title>Import Fixture</title>\n <script type=\"module\">\n import App from \"./app.js\";\n import { render } from \"./render.js\";\n\n render(App);\n </script>\n </head>\n <body></body>\n</html>\n",
2, NULL},
{"java", CBM_LANG_JAVA, "Fixture.java",
"import java.util.List;\nimport java.util.Map;\n\nclass Fixture {\n List<String> names;\n Map<String, Integer> counts;\n}\n",
2, NULL},
{"javascript", CBM_LANG_JAVASCRIPT, "imports_fixture.js",
"import path from \"node:path\";\nimport { readFile, writeFile } from \"node:fs/promises\";\n\nexport async function copyConfig(src) {\n const dst = path.join(\"/tmp\", \"config.json\");\n const data = await readFile(src, \"utf8\");\n await writeFile(dst, data);\n return dst;\n}\n",
3, NULL},
{"kotlin", CBM_LANG_KOTLIN, "fixture.kt",
"package com.example.app\n\nimport kotlin.collections.List\nimport java.util.UUID\nimport kotlinx.coroutines.runBlocking\n",
3, NULL},
{"lean", CBM_LANG_LEAN, "imports.lean",
"import Std.Data.List.Basic\nimport Mathlib.Tactic\n\ndef hello : String := \"world\"\n", 2, NULL},
{"lua", CBM_LANG_LUA, "a.lua",
"local socket = require(\"socket\")\nlocal json = require(\"dkjson\")\n\nlocal function connect()\n return socket, json\nend\n\nreturn connect\n",
2, NULL},
{"magma", CBM_LANG_MAGMA, "a.magma",
"load \"mylib.m\";\nload \"helpers.m\";\n\nfunction foo()\n return 1;\nend function;\n", 2, NULL},
{"objc", CBM_LANG_OBJC, "fixture_imports.m",
"#import <Foundation/Foundation.h>\n#import \"AppModel.h\"\n", 2, NULL},
{"ocaml", CBM_LANG_OCAML, "a.ml",
"open List\nopen String\n\nlet greet name = String.cat \"Hi \" name\n", 2, NULL},
{"perl", CBM_LANG_PERL, "imports.pl",
"use strict;\nuse warnings;\nuse Data::Dumper;\n\nsub run { print Dumper([1, 2, 3]); }\n", 3, NULL},
{"php", CBM_LANG_PHP, "a.php",
"<?php\nrequire_once 'vendor/autoload.php';\ninclude 'config.php';\n", 2, NULL},
{"python", CBM_LANG_PYTHON, "fixture_imports.py",
"import os\nfrom typing import List, Optional\n\n\ndef widths(items: List[str], pad: Optional[int]) -> List[int]:\n return [len(s) + (pad or 0) for s in items if os.path.exists(s)]\n",
3, NULL},
{"r", CBM_LANG_R, "imports_fixture.R", "library(jsonlite)\nrequire(stats)\nsource(\"utils.R\")\n", 3,
NULL},
{"ruby", CBM_LANG_RUBY, "a.rb", "require \"json\"\nrequire_relative \"helper\"\n", 2, NULL},
{"rust", CBM_LANG_RUST, "imports.rs",
"use std::collections::HashMap;\nuse std::fmt::{self, Display};\nuse serde::Serialize;\n\nfn main() {\n let _m: HashMap<String, i32> = HashMap::new();\n}\n",
3, NULL},
{"scala", CBM_LANG_SCALA, "a.scala",
"package example\n\nimport scala.collection.mutable.ListBuffer\nimport java.time.Instant\n\nobject Main {\n def run(): Unit = {\n val buf = ListBuffer[Instant]()\n buf += Instant.now()\n }\n}\n",
2, NULL},
{"scss", CBM_LANG_SCSS, "imports.scss",
"@import \"variables\";\n@import \"mixins\";\n\n.button {\n color: red;\n}\n", 2, NULL},
{"svelte", CBM_LANG_SVELTE, "a.svelte",
"<script>\n import { onMount } from 'svelte';\n import App from './App.svelte';\n\n onMount(() => {});\n</script>\n\n<App />\n",
2, NULL},
{"swift", CBM_LANG_SWIFT, "a.swift", "import Foundation\nimport SwiftUI\n", 2, NULL},
{"tsx", CBM_LANG_TSX, "a.tsx",
"import React from \"react\";\nimport { useState, useEffect } from \"react\";\nimport * as styles from \"./Button.module.css\";\n\nexport function Button(): React.ReactElement {\n const [count, setCount] = useState(0);\n useEffect(() => {}, []);\n return <button className={styles.root} onClick={() => setCount(count + 1)}>{count}</button>;\n}\n",
4, NULL},
{"typescript", CBM_LANG_TYPESCRIPT, "imports_fixture.ts",
"import express from \"express\";\nimport * as path from \"path\";\nimport { useState, useEffect } from \"react\";\n\nconst app = express();\nconst dir: string = path.join(\".\");\nconsole.log(app, dir, useState, useEffect);\n",
4, NULL},
{"vue", CBM_LANG_VUE, "component.vue",
"<script setup>\nimport { ref } from 'vue'\nimport HelloWorld from './HelloWorld.vue'\n\nconst msg = ref('hello')\n</script>\n\n<template>\n <HelloWorld :msg=\"msg\" />\n</template>\n",
2, NULL},
{"wolfram", CBM_LANG_WOLFRAM, "main.wl",
"Needs[\"MyPackage`Utils`\"]\n<< \"helpers.wl\"\n\nmain[x_] := process[x] + 1\n", 2, NULL},
{"zig", CBM_LANG_ZIG, "imports.zig",
"const std = @import(\"std\");\nconst builtin = @import(\"builtin\");\n\npub fn main() void {}\n", 2,
"@import is nested in variable_declaration; parse_generic_imports scans only root children for builtin_function -> 0"},
};
TEST(grammar_imports_extracted) {
int n = (int)(sizeof(IMPORT_CASES) / sizeof(IMPORT_CASES[0]));
int failures = 0;
for (int i = 0; i < n; i++) {
const ImportCase *c = &IMPORT_CASES[i];
CBMFileResult *r = cbm_extract_file(c->content, (int)strlen(c->content), c->lang, "imp",
c->filename, 0, NULL, NULL);
if (!r) {
fprintf(stderr, " [IMPORTS] FAIL %-12s extract returned NULL\n", c->name);
failures++;
continue;
}
int got = r->imports.count;
cbm_free_result(r);
if (got < c->expected) {
fprintf(stderr, " [IMPORTS] FAIL %-12s imports=%d expected>=%d%s%s\n", c->name, got,
c->expected, c->gap_note ? " — " : "", c->gap_note ? c->gap_note : "");
failures++;
}
}
fprintf(stderr, " [IMPORTS] %d import-capable grammars: %d FAILURES (each = a grammar whose "
"imports are not extracted)\n",
n, failures);
ASSERT_EQ(failures, 0);
PASS();
}
void suite_grammar_imports(void) {
RUN_TEST(grammar_imports_extracted);
}
+353
View File
@@ -0,0 +1,353 @@
/*
* test_grammar_labels.c — Per-grammar node-type LABEL golden snapshot.
*
* For every supported grammar, snapshot the deterministic node-type-label
* histogram that cbm_extract_file() produces for the shared fixture (e.g.
* "Class:1,Function:2,Module:1"). Asserting the histogram stays fixed catches
* ANY future change to how a grammar's constructs are labeled — e.g. a class
* silently downgraded to a Function, a lost Method, or a new spurious node.
*
* Node counts are deterministic (unlike edge counts), so an exact histogram
* golden is stable. Labels come from CBMDefinition.label assigned during
* extraction (the same labels that become graph-node labels downstream).
*
* Capture workflow: a grammar with no golden row prints a `[LABEL-CAPTURE]`
* line and fails; copy those lines into LABEL_GOLDENS, then the test asserts.
*/
#include "test_framework.h"
#include "cbm.h"
#include "grammar_cases.h"
#include <stdlib.h>
#include <string.h>
/* Build a sorted "Label:count,Label:count" histogram of a result's def labels
* (includes the always-emitted Module node). Deterministic + stable. */
static void label_histogram(CBMFileResult *r, char *out, size_t out_sz) {
/* Collect distinct labels + counts (small N — linear scan is fine). */
enum { MAXL = 32 };
const char *labels[MAXL];
int counts[MAXL];
int nl = 0;
for (int i = 0; i < r->defs.count; i++) {
const char *l = r->defs.items[i].label ? r->defs.items[i].label : "(null)";
int j = 0;
for (; j < nl; j++) {
if (strcmp(labels[j], l) == 0) {
counts[j]++;
break;
}
}
if (j == nl && nl < MAXL) {
labels[nl] = l;
counts[nl] = 1;
nl++;
}
}
/* Insertion sort labels alphabetically for a canonical string. */
for (int i = 1; i < nl; i++) {
const char *lk = labels[i];
int ck = counts[i];
int j = i - 1;
while (j >= 0 && strcmp(labels[j], lk) > 0) {
labels[j + 1] = labels[j];
counts[j + 1] = counts[j];
j--;
}
labels[j + 1] = lk;
counts[j + 1] = ck;
}
out[0] = '\0';
size_t used = 0;
for (int i = 0; i < nl; i++) {
int w = snprintf(out + used, out_sz - used, "%s%s:%d", i ? "," : "", labels[i], counts[i]);
if (w < 0 || (size_t)w >= out_sz - used) {
break;
}
used += (size_t)w;
}
}
typedef struct {
const char *name;
const char *hist;
} LabelGolden;
/* Golden node-type-label histograms — captured from the current (correct)
* extractor output. A mismatch = a labeling regression for that grammar.
* (Populated from [LABEL-CAPTURE] output; see header.) */
static const LabelGolden LABEL_GOLDENS[] = {
{"go", "Function:2,Module:1"},
{"c", "Function:2,Module:1"},
{"cpp", "Class:1,Function:1,Module:1"},
{"cuda", "Function:2,Module:1"},
{"python", "Class:1,Function:1,Module:1"},
{"javascript", "Class:1,Function:1,Module:1"},
{"typescript", "Class:1,Function:1,Module:1"},
{"tsx", "Function:1,Module:1"},
{"java", "Class:1,Method:1,Module:1"},
{"kotlin", "Class:1,Function:1,Module:1"},
{"rust", "Class:1,Function:1,Module:1"},
{"ruby", "Class:1,Function:1,Module:1"},
{"php", "Class:1,Function:1,Module:1"},
{"c_sharp", "Class:1,Method:1,Module:1"},
{"bash", "Function:2,Module:1"},
{"zsh", "Function:2,Module:1"},
{"lua", "Function:2,Module:1"},
{"luau", "Function:2,Module:1"},
{"perl", "Function:2,Module:1"},
{"dart", "Class:1,Function:1,Module:1"},
{"swift", "Class:1,Function:1,Module:1"},
{"scala", "Class:1,Function:1,Method:1,Module:1"},
{"gdscript", "Function:1,Module:1"},
{"groovy", "Class:1,Method:1,Module:1"},
{"zig", "Function:2,Module:1"},
{"nim", "Function:2,Module:1"},
{"solidity", "Class:1,Function:1,Method:1,Module:1"},
{"tcl", "Function:2,Module:1"},
{"powershell", "Function:2,Module:1"},
{"r", "Function:2,Module:1"},
{"julia", "Function:1,Module:1"},
{"matlab", "Function:2,Module:1"},
{"ada", "Function:1,Module:1"},
{"agda", "Module:1"},
{"apex", "Class:1,Method:1,Module:1"},
{"awk", "Function:2,Module:1"},
{"cairo", "Function:2,Module:1"},
{"clojure", "Function:2,Module:1"},
{"commonlisp", "Function:2,Module:1"},
{"emacslisp", "Function:2,Module:1"},
{"crystal", "Class:1,Function:1,Module:1"},
{"d", "Function:2,Module:1"},
{"elixir", "Class:1,Function:1,Module:1"},
{"erlang", "Function:2,Module:1"},
{"fennel", "Function:2,Module:1"},
{"fish", "Function:1,Module:1"},
{"fortran", "Function:1,Module:1"},
{"fsharp", "Function:2,Module:1"},
{"gleam", "Function:2,Module:1"},
{"glsl", "Function:2,Module:1"},
{"hare", "Function:2,Module:1"},
{"haskell", "Function:1,Module:1"},
{"hlsl", "Function:2,Module:1"},
{"ispc", "Function:2,Module:1"},
{"objc", "Class:1,Method:1,Module:1"},
{"ocaml", "Function:2,Module:1"},
{"odin", "Function:2,Module:1"},
{"pascal", "Function:1,Module:1"},
{"pony", "Module:1"},
{"purescript", "Function:1,Module:1"},
{"racket", "Function:2,Module:1"},
{"rescript", "Function:2,Module:1"},
{"scheme", "Function:2,Module:1"},
{"slang", "Function:2,Module:1"},
{"squirrel", "Function:2,Module:1"},
{"starlark", "Function:2,Module:1"},
{"sway", "Function:2,Module:1"},
{"teal", "Function:2,Module:1"},
{"vimscript", "Function:1,Module:1"},
{"elm", "Class:1,Function:1,Module:1"},
{"func", "Function:1,Module:1"},
{"lean", "Function:2,Module:1"},
{"move", "Module:1"},
{"smali", "Module:1"},
{"systemverilog", "Module:1"},
{"verilog", "Module:1"},
{"vhdl", "Module:1"},
{"wgsl", "Function:2,Module:1"},
{"tlaplus", "Function:1,Module:1"},
{"llvm", "Function:1,Module:1"},
{"tablegen", "Module:1"},
{"puppet", "Module:1"},
{"assembly", "Module:1"},
{"nasm", "Module:1"},
{"cfml", "Function:1,Module:1"},
{"cfscript", "Function:1,Module:1"},
{"cobol", "Module:1"},
{"janet", "Module:1"},
{"magma", "Function:1,Module:1"},
{"qml", "Function:1,Module:1"},
{"wolfram", "Function:1,Module:1"},
{"pine", "Module:1"},
{"form", "Module:1,Variable:1"},
{"protobuf", "Module:1"},
{"soql", "Module:1"},
{"sosl", "Module:1"},
{"dotenv", "Module:1"},
{"json", "Module:1,Variable:2"},
{"json5", "Module:1"},
{"jsonnet", "Module:1"},
{"jsdoc", "Module:1"},
{"yaml", "Module:1,Variable:2"},
{"k8s", "Module:1"},
{"kustomize", "Module:1"},
{"toml", "Class:1,Module:1,Variable:1"},
{"ini", "Class:1,Module:1,Variable:1"},
{"csv", "Module:1"},
{"sql", "Module:1,Variable:1"},
{"xml", "Class:2,Module:1"},
{"html", "Module:1"},
{"css", "Module:1"},
{"scss", "Module:1,Variable:1"},
{"markdown", "Module:1,Section:1"},
{"rst", "Module:1"},
{"dockerfile", "Module:1"},
{"makefile", "Function:1,Module:1"},
{"cmake", "Module:1"},
{"meson", "Module:1"},
{"gn", "Module:1"},
{"just", "Module:1"},
{"hcl", "Class:1,Module:1"},
{"nix", "Module:1"},
{"gomod", "Module:1"},
{"gotemplate", "Module:1"},
{"graphql", "Module:1"},
{"prisma", "Module:1"},
{"thrift", "Module:1"},
{"capnp", "Module:1"},
{"smithy", "Module:1"},
{"wit", "Module:1"},
{"kdl", "Module:1"},
{"ron", "Module:1"},
{"nickel", "Module:1"},
{"pkl", "Module:1"},
{"bicep", "Module:1"},
{"bitbake", "Module:1"},
{"beancount", "Module:1"},
{"bibtex", "Module:1"},
{"po", "Module:1"},
{"diff", "Module:1"},
{"regex", "Module:1"},
{"requirements", "Module:1"},
{"properties", "Module:1"},
{"gitignore", "Module:1"},
{"gitattributes", "Module:1"},
{"sshconfig", "Module:1"},
{"hyprlang", "Module:1"},
{"kconfig", "Class:1,Module:1"},
{"linkerscript", "Module:1"},
{"devicetree", "Module:1"},
{"jinja2", "Module:1"},
{"liquid", "Module:1"},
{"blade", "Module:1"},
{"vue", "Module:1"},
{"svelte", "Module:1"},
{"astro", "Module:1"},
{"templ", "Class:1,Module:1"},
{"typst", "Module:1"},
{"mermaid", "Module:1"},
{NULL, NULL},
};
static const char *golden_for(const char *name) {
for (int i = 0; LABEL_GOLDENS[i].name; i++) {
if (strcmp(LABEL_GOLDENS[i].name, name) == 0) {
return LABEL_GOLDENS[i].hist;
}
}
return NULL;
}
TEST(grammar_label_goldens) {
int n = (int)CBM_GRAMMAR_CASES_COUNT;
int failures = 0;
int missing = 0;
for (int i = 0; i < n; i++) {
const GrammarCase *c = &CBM_GRAMMAR_CASES[i];
CBMFileResult *r =
cbm_extract_file(c->src, (int)strlen(c->src), c->lang, "lbl", c->path, 0, NULL, NULL);
if (!r) {
fprintf(stderr, " [LABEL] %-14s extract returned NULL\n", c->name);
failures++;
continue;
}
char hist[256];
label_histogram(r, hist, sizeof(hist));
cbm_free_result(r);
const char *golden = golden_for(c->name);
if (!golden) {
fprintf(stderr, " [LABEL-CAPTURE] {\"%s\", \"%s\"},\n", c->name, hist);
missing++;
} else if (strcmp(golden, hist) != 0) {
fprintf(stderr, " [LABEL] %-14s MISMATCH golden=[%s] actual=[%s]\n", c->name, golden,
hist);
failures++;
}
}
if (missing > 0) {
fprintf(stderr, " [LABEL] %d grammar(s) missing a golden — copy the [LABEL-CAPTURE] lines "
"into LABEL_GOLDENS\n",
missing);
}
ASSERT_EQ(failures, 0);
ASSERT_EQ(missing, 0);
PASS();
}
/* Count def nodes that are NOT the structural file/module wrappers. */
static int non_module_defs(CBMFileResult *r) {
int n = 0;
for (int i = 0; i < r->defs.count; i++) {
const char *l = r->defs.items[i].label;
if (l && strcmp(l, "Module") != 0 && strcmp(l, "File") != 0 && strcmp(l, "Folder") != 0 &&
strcmp(l, "Package") != 0 && strcmp(l, "Directory") != 0) {
n++;
}
}
return n;
}
/* Code / IDL grammars whose fixture contains a real definition construct
* (function / type / message / model / module / label) that MUST extract to at
* least one non-Module node. These currently extract only a Module node — an
* under-extraction gap reproduced here as a hard failure (fix phase). Data /
* config / markup grammars (json/yaml/css/…) are intentionally excluded — a
* lone Module node is correct for them. */
static const char *MUST_EXTRACT_DEFS[] = {
"agda", "pony", "move", "cobol", "janet", "pine", "smali", "verilog",
"vhdl", "systemverilog", "protobuf", "graphql", "thrift", "capnp", "smithy",
"wit", "prisma", "cmake", "puppet", "tablegen", "assembly", "nasm", NULL};
TEST(grammar_code_extracts_defs) {
int failures = 0;
for (int k = 0; MUST_EXTRACT_DEFS[k]; k++) {
const char *name = MUST_EXTRACT_DEFS[k];
const GrammarCase *c = NULL;
for (int i = 0; i < (int)CBM_GRAMMAR_CASES_COUNT; i++) {
if (strcmp(CBM_GRAMMAR_CASES[i].name, name) == 0) {
c = &CBM_GRAMMAR_CASES[i];
break;
}
}
if (!c) {
fprintf(stderr, " [CODE-DEFS] FAIL %-14s — no fixture in CBM_GRAMMAR_CASES\n", name);
failures++;
continue;
}
CBMFileResult *r =
cbm_extract_file(c->src, (int)strlen(c->src), c->lang, "cd", c->path, 0, NULL, NULL);
if (!r) {
fprintf(stderr, " [CODE-DEFS] FAIL %-14s extract returned NULL\n", name);
failures++;
continue;
}
int nd = non_module_defs(r);
cbm_free_result(r);
if (nd < 1) {
fprintf(stderr, " [CODE-DEFS] FAIL %-14s extracts 0 non-Module defs (under-extraction "
"gap — only a Module node)\n",
name);
failures++;
}
}
fprintf(stderr, " [CODE-DEFS] %d code/IDL grammars: %d under-extraction FAILURES\n",
(int)(sizeof(MUST_EXTRACT_DEFS) / sizeof(MUST_EXTRACT_DEFS[0])) - 1, failures);
ASSERT_EQ(failures, 0);
PASS();
}
void suite_grammar_labels(void) {
RUN_TEST(grammar_label_goldens);
RUN_TEST(grammar_code_extracts_defs);
}
+6 -12
View File
@@ -15,6 +15,7 @@
*/
#include "test_framework.h"
#include "cbm.h"
#include "grammar_cases.h"
static int reg_has_def_any(CBMFileResult *r, const char *name) {
for (int i = 0; i < r->defs.count; i++) {
@@ -29,16 +30,7 @@ static CBMFileResult *extract(const char *src, CBMLanguage lang, const char *pro
return cbm_extract_file(src, (int)strlen(src), lang, proj, path, 0, NULL, NULL);
}
typedef struct {
const char *name;
CBMLanguage lang;
const char *path;
const char *src;
int min_defs;
const char *expect[4];
} GrammarCase;
static const GrammarCase CASES[] = {
const GrammarCase CBM_GRAMMAR_CASES[] = {
/* ── LSP-backed / mainstream code languages (expect named defs) ── */
{"go", CBM_LANG_GO, "a.go", "package m\nfunc Foo() {}\nfunc Bar() int { return 0 }\n", 2, {"Foo", "Bar", NULL}},
{"c", CBM_LANG_C, "a.c", "int foo(void){return 0;}\nint bar(void){return 1;}\n", 2, {"foo", "bar", NULL}},
@@ -207,11 +199,13 @@ static const GrammarCase CASES[] = {
{"mermaid", CBM_LANG_MERMAID, "a.mmd", "graph TD\n A --> B\n", 0, {NULL}},
};
const size_t CBM_GRAMMAR_CASES_COUNT = sizeof(CBM_GRAMMAR_CASES) / sizeof(CBM_GRAMMAR_CASES[0]);
TEST(grammar_regression_all) {
int failures = 0;
size_t n = sizeof(CASES) / sizeof(CASES[0]);
size_t n = CBM_GRAMMAR_CASES_COUNT;
for (size_t i = 0; i < n; i++) {
const GrammarCase *c = &CASES[i];
const GrammarCase *c = &CBM_GRAMMAR_CASES[i];
CBMFileResult *r = extract(c->src, c->lang, "reg", c->path);
if (!r) {
fprintf(stderr, " [REG] %-14s extract returned NULL\n", c->name);
File diff suppressed because it is too large. Load diff
+9
View File
@@ -21,6 +21,8 @@ extern void suite_str_util(void);
extern void suite_platform(void);
extern void suite_extraction(void);
extern void suite_grammar_regression(void);
extern void suite_grammar_labels(void);
extern void suite_grammar_imports(void);
extern void suite_ac(void);
extern void suite_store_nodes(void);
extern void suite_store_edges(void);
@@ -73,6 +75,7 @@ extern void suite_ui(void);
extern void suite_security(void);
extern void suite_yaml(void);
extern void suite_integration(void);
extern void suite_lang_contract(void);
extern void suite_incremental(void);
extern void suite_simhash(void);
extern void suite_stack_overflow(void);
@@ -93,6 +96,8 @@ int main(void) {
RUN_SUITE(ac);
RUN_SUITE(extraction);
RUN_SUITE(grammar_regression);
RUN_SUITE(grammar_labels);
RUN_SUITE(grammar_imports);
/* Store (M5) */
RUN_SUITE(store_nodes);
@@ -196,6 +201,10 @@ int main(void) {
/* Integration (end-to-end) */
RUN_SUITE(integration);
/* Per-language graph contracts (node/edge types, attribution, no-crash) */
RUN_SUITE(lang_contract);
RUN_SUITE(incremental);
/* Release sqlite3 internal caches so ASan doesn't report them as leaks */