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

358 lines
13 KiB
C

/*
* 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:6,Function:3,Method:5,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"},
/* +Class:1 (kotlin.Any) +Method:3 (toString/equals/hashCode) injected by
* kotlin_builtins.c, as python's golden includes its injected builtins. */
{"kotlin", "Class:2,Function:1,Method:3,Module:1"},
{"rust", "Function:1,Module:1,Struct: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"},
{"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", "Class:1,Function:1,Module:1"},
{"matlab", "Function:2,Module:1"},
{"ada", "Function:1,Module:1"},
{"agda", "Function:1,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", "Class:1,Method:1,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", "Function:1,Module:1"},
{"mojo", "Class:1,Function:1,Method:1,Module:1"},
{"smali", "Class:1,Function:1,Module:1"},
{"systemverilog", "Class:1,Function:1,Module:1"},
{"verilog", "Class:1,Module:1"},
{"vhdl", "Class:1,Module:1"},
{"wgsl", "Function:2,Module:1"},
{"tlaplus", "Function:1,Module:1"},
{"llvm", "Function:1,Module:1"},
{"tablegen", "Function:1,Module:1"},
{"puppet", "Class:1,Module:1"},
{"assembly", "Function:1,Module:1"},
{"nasm", "Function:1,Module:1"},
{"cfml", "Function:1,Module:1"},
{"cfscript", "Function:1,Module:1"},
{"cobol", "Function:1,Module:1"},
{"janet", "Function:2,Module:1"},
{"magma", "Function:1,Module:1"},
{"qml", "Function:1,Module:1"},
{"wolfram", "Function:1,Module:1"},
{"pine", "Function:1,Module:1"},
{"form", "Module:1,Variable:1"},
{"protobuf", "Class:1,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", "Function:1,Module:1"},
{"meson", "Module:1"},
{"gn", "Module:1"},
{"just", "Function:1,Module:1"},
{"hcl", "Class:1,Module:1"},
{"nix", "Module:1"},
{"gomod", "Module:1"},
{"gotemplate", "Module:1"},
{"graphql", "Class:1,Field:1,Module:1"},
{"prisma", "Class:1,Field:1,Module:1"},
{"thrift", "Function:1,Module:1"},
{"capnp", "Class:1,Module:1"},
{"smithy", "Class:1,Module:1"},
{"wit", "Class:2,Function:1,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,Variable:2"},
{"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);
}