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

1088 lines
48 KiB
C

/*
* repro_grammar_build.c -- Per-grammar INVARIANT battery for the
* BUILD / INFRA language family.
*
* One TEST() per language so per-language RED/GREEN shows on the bug-repro
* board. Each test runs a battery adapted to what the language actually models.
*
* Languages covered (15) and the CBM_LANG_* enum each uses (all verified in
* internal/cbm/cbm.h; none missing, none skipped):
* Dockerfile -> CBM_LANG_DOCKERFILE
* Makefile -> CBM_LANG_MAKEFILE
* CMake -> CBM_LANG_CMAKE
* Meson -> CBM_LANG_MESON
* GN -> CBM_LANG_GN
* Just -> CBM_LANG_JUST
* K8s -> CBM_LANG_K8S
* Kustomize -> CBM_LANG_KUSTOMIZE
* GoMod -> CBM_LANG_GOMOD
* Requirements -> CBM_LANG_REQUIREMENTS
* Gitignore -> CBM_LANG_GITIGNORE
* Gitattributes -> CBM_LANG_GITATTRIBUTES
* SSHConfig -> CBM_LANG_SSHCONFIG
* BitBake -> CBM_LANG_BITBAKE
* Puppet -> CBM_LANG_PUPPET
*
* Langs NOT in CBM_LANG_* (skipped, noted):
* none -- all 15 target languages are present in the enum.
*
* BATTERY DIMENSIONS
* ------------------
* SINGLE-FILE (cbm_extract_file, via inv_rx + inv_count_* helpers):
* 1. extract-clean : inv_extract_clean(src,lang,file) == 1
* (parser returned a result and did not set has_error).
* 2. labels-valid : inv_count_bad_labels(r) == 0
* (every extracted def label is in the known label set).
* 3. fqn-wellformed : inv_count_bad_fqns(r) == 0
* (no empty / ".." / leading-trailing '.' / whitespace QNs).
* 4. ranges-valid : inv_count_bad_ranges(r) == 0
* (start_line >= 1 and start_line <= end_line).
* 5. defs-present : at least one def with the expected label is extracted.
* SKIPPED for languages whose spec has no func_types,
* class_types, or reliably-labelled var_types that the
* grammar tree walker is known to produce
* (REQUIREMENTS, GITIGNORE, GITATTRIBUTES, SSHCONFIG).
* 6. calls-extracted : inv_has_call(r, callee) == 1.
* Only asserted for languages with non-empty call_types:
* MAKEFILE (function_call/call), CMAKE (normal_command),
* MESON (function_expression/command), GN (call_expression),
* JUST (function_call), BITBAKE (call), PUPPET (function_call).
*
* FULL-PIPELINE (rh_index_files -> cbm_store_t*, via inv_count_* store helpers):
* 7. callable-sourcing : inv_count_calls_by_source(store,project,&mod,&call).
* Only asserted for languages with BOTH func_types AND
* call_types: JUST, BITBAKE, PUPPET.
* 8. no-dangling : inv_count_dangling_edges(store, project, "CALLS") == 0.
* Asserted together with dim 7 when the pipeline is run.
*
* ROBUSTNESS (every language):
* R. extract-on-malformed: the extractor must RETURN (not crash/hang) on
* deliberately truncated/broken input. inv_extract_clean may return 0
* (has_error is fine) but must not return NULL.
* Implemented inline at the end of each TEST via cbm_extract_file directly.
*
* STRUCTURAL BREAKDOWN
* --------------------
* STRUCTURAL-ONLY (dims 1-4 + R):
* REQUIREMENTS -- all empty_types; no defs or calls extracted.
* GITIGNORE -- all empty_types; no defs or calls extracted.
* GITATTRIBUTES -- all empty_types; no defs or calls extracted.
* SSHCONFIG -- all empty_types; no defs or calls extracted.
*
* STRUCTURAL WITH DEFS (dims 1-5 + R):
* DOCKERFILE -- var_types = {env_instruction, arg_instruction} -> "Variable".
* GOMOD -- var_types = {require_directive, replace_directive} -> "Variable".
* K8S -- semantic extractor (cbm_extract_k8s); extracts kind -> "Resource".
* KUSTOMIZE -- semantic extractor (cbm_extract_k8s); extracts kind -> "Resource".
*
* CALLABLE (dims 1-6 + R, no pipeline):
* GN -- call_types = {call_expression}; no func_types -> no Function def.
* Dim 5 SKIPPED (no defs); dim 6 only.
* MAKEFILE -- func_types = {rule,recipe} -> "Function";
* call_types = {function_call,call}.
* Dims 1-6. Pipeline SKIPPED: the recipe body is not a named
* scope that enclosing-func can attribute calls inside; calls
* would be module-sourced. No pipeline dim.
* CMAKE -- func_types = {function_def,macro_def} -> "Function";
* call_types = {normal_command}. Dims 1-6. Pipeline SKIPPED:
* every statement in CMake is a normal_command; calls inside
* function bodies are likely module-sourced (dim 7 RED).
* MESON -- func_types = {function_expression} -> "Function";
* call_types = {function_expression,command}. Dims 1-6.
* Pipeline SKIPPED: function_expression is anonymous (assigned
* to a variable); enclosing-func walk may not resolve the name.
*
* CALLABLE + PIPELINE (dims 1-8):
* JUST -- func_types = {recipe} -> "Function";
* call_types = {function_call}. Dims 1-8.
* Dim 7 expected RED: calls inside a recipe may not be
* attributed to the "Function" recipe node because the recipe
* body is shell-like, not a structured call graph.
* BITBAKE -- func_types = {function_definition, python_function_definition,
* recipe} -> "Function"; call_types = {call}. Dims 1-8.
* Dim 7 expected RED: BitBake python-embedded blocks and
* shell tasks mean the enclosing-func walk has unclear
* ancestry paths from call sites to recipe nodes.
* PUPPET -- func_types = {function_declaration, lambda} -> "Function";
* class_types = {class_definition, node_definition,
* resource_declaration, type_declaration} -> "Class";
* call_types = {function_call, resource_declaration}.
* Dims 1-8. Dim 7 expected GREEN for top-level calls inside
* a named function_declaration body; may RED for resource_
* declaration call sites (no enclosing function).
*
* Coding rule: inline comments are line comments only (no nested block-comment opener).
*/
#include "test_framework.h"
#include "repro_invariant_lib.h"
#include <store/store.h>
#include <stdio.h>
#include <string.h>
/* ── Structural-base battery (dims 1-4) ──────────────────────────────────────
*
* Runs the four core invariants on valid input. No defs-present assertion.
* Used for REQUIREMENTS, GITIGNORE, GITATTRIBUTES, SSHCONFIG where the spec
* has no func_types, class_types, or labelled var_types that yield defs.
* Returns 0 on PASS, 1 on FAIL.
*/
static int build_base_battery(const char *lang_tag, const char *src,
CBMLanguage lang, const char *file) {
const char *RED = tf_red();
const char *RST = tf_reset();
/* 1. extract-clean */
if (inv_extract_clean(src, lang, file) != 1) {
printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n",
RED, RST, lang_tag);
return 1;
}
CBMFileResult *r = inv_rx(src, lang, file);
if (!r) {
printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n",
RED, RST, lang_tag);
return 1;
}
int fails = 0;
/* 2. labels-valid */
int bad_labels = inv_count_bad_labels(r);
if (bad_labels != 0) {
printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n",
RED, RST, lang_tag, bad_labels);
fails++;
}
/* 3. fqn-wellformed */
int bad_fqns = inv_count_bad_fqns(r);
if (bad_fqns != 0) {
printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n",
RED, RST, lang_tag, bad_fqns);
fails++;
}
/* 4. ranges-valid */
int bad_ranges = inv_count_bad_ranges(r);
if (bad_ranges != 0) {
printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n",
RED, RST, lang_tag, bad_ranges);
fails++;
}
cbm_free_result(r);
return fails ? 1 : 0;
}
/* ── Structural battery with defs-present (dims 1-5) ────────────────────────
*
* Adds the defs-present dimension for languages with class_types, func_types,
* or reliably-labelled var_types (DOCKERFILE, GOMOD, K8S, KUSTOMIZE).
* Pass NULL for expect_label2 when only one label type is needed.
* Returns 0 on PASS, 1 on FAIL.
*/
static int build_struct_battery(const char *lang_tag, const char *src,
CBMLanguage lang, const char *file,
const char *expect_label,
const char *expect_label2) {
const char *RED = tf_red();
const char *RST = tf_reset();
/* 1. extract-clean */
if (inv_extract_clean(src, lang, file) != 1) {
printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n",
RED, RST, lang_tag);
return 1;
}
CBMFileResult *r = inv_rx(src, lang, file);
if (!r) {
printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n",
RED, RST, lang_tag);
return 1;
}
int fails = 0;
/* 2. labels-valid */
int bad_labels = inv_count_bad_labels(r);
if (bad_labels != 0) {
printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n",
RED, RST, lang_tag, bad_labels);
fails++;
}
/* 3. fqn-wellformed */
int bad_fqns = inv_count_bad_fqns(r);
if (bad_fqns != 0) {
printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n",
RED, RST, lang_tag, bad_fqns);
fails++;
}
/* 4. ranges-valid */
int bad_ranges = inv_count_bad_ranges(r);
if (bad_ranges != 0) {
printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n",
RED, RST, lang_tag, bad_ranges);
fails++;
}
/* 5. defs-present (primary label) */
if (expect_label && inv_count_label(r, expect_label) < 1) {
printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n",
RED, RST, lang_tag, expect_label);
fails++;
}
/* 5b. defs-present (secondary label, optional) */
if (expect_label2 && inv_count_label(r, expect_label2) < 1) {
printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n",
RED, RST, lang_tag, expect_label2);
fails++;
}
cbm_free_result(r);
return fails ? 1 : 0;
}
/* ── Callable battery (dims 1-6) ─────────────────────────────────────────────
*
* Adds dims 5 (optional) and 6 (calls-extracted) to the base invariants.
* Pass NULL for expect_label when the language has no func/class def to assert
* alongside the call (e.g. GN has call_types but no func_types).
* Returns 0 on PASS, 1 on FAIL.
*/
static int build_callable_battery(const char *lang_tag, const char *src,
CBMLanguage lang, const char *file,
const char *expect_label,
const char *callee) {
const char *RED = tf_red();
const char *RST = tf_reset();
/* 1. extract-clean */
if (inv_extract_clean(src, lang, file) != 1) {
printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n",
RED, RST, lang_tag);
return 1;
}
CBMFileResult *r = inv_rx(src, lang, file);
if (!r) {
printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n",
RED, RST, lang_tag);
return 1;
}
int fails = 0;
/* 2. labels-valid */
int bad_labels = inv_count_bad_labels(r);
if (bad_labels != 0) {
printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n",
RED, RST, lang_tag, bad_labels);
fails++;
}
/* 3. fqn-wellformed */
int bad_fqns = inv_count_bad_fqns(r);
if (bad_fqns != 0) {
printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n",
RED, RST, lang_tag, bad_fqns);
fails++;
}
/* 4. ranges-valid */
int bad_ranges = inv_count_bad_ranges(r);
if (bad_ranges != 0) {
printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n",
RED, RST, lang_tag, bad_ranges);
fails++;
}
/* 5. defs-present (only when a def label is expected) */
if (expect_label && inv_count_label(r, expect_label) < 1) {
printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n",
RED, RST, lang_tag, expect_label);
fails++;
}
/* 6. calls-extracted */
if (callee && inv_has_call(r, callee) != 1) {
printf(" %sFAIL%s [%s] calls-extracted: no call to \"%s\" found\n",
RED, RST, lang_tag, callee);
fails++;
}
cbm_free_result(r);
return fails ? 1 : 0;
}
/* ── Full-pipeline battery (dims 7-8) ───────────────────────────────────────
*
* Indexes the single-file fixture through the production pipeline and asserts
* callable-sourcing + no-dangling. Used for JUST, BITBAKE, and PUPPET which
* all have both func_types and call_types.
*
* Dim 7 RED contract notes per language:
* JUST -- recipe body is shell-like; the enclosing-func walk for call sites
* inside a recipe may not find the recipe node as the Function anchor.
* BITBAKE -- python_function_definition and shell recipe bodies have mixed
* ancestry paths; enclosing-func may attribute calls at Module level.
* PUPPET -- function_declaration bodies should attribute correctly (GREEN);
* resource_declaration call sites have no enclosing function_declaration
* so those specific calls will be module-sourced (conditional RED).
* Returns 0 on PASS, 1 on FAIL.
*/
static int build_pipeline_battery(const char *lang_tag, const char *filename,
const char *src) {
const char *RED = tf_red();
const char *RST = tf_reset();
RFile files[1];
files[0].name = filename;
files[0].content = src;
RProj lp;
cbm_store_t *store = rh_index_files(&lp, files, 1);
if (!store) {
printf(" %sFAIL%s [%s] pipeline: rh_index_files returned NULL\n",
RED, RST, lang_tag);
return 1;
}
int fails = 0;
/* 7. callable-sourcing */
int module_sourced = 0;
int callable_sourced = 0;
inv_count_calls_by_source(store, lp.project, &module_sourced,
&callable_sourced);
if (module_sourced != 0) {
printf(" %sFAIL%s [%s] callable-sourcing: %d in-body CALLS sourced at "
"Module (callable=%d) -- known enclosing-func gap\n",
RED, RST, lang_tag, module_sourced, callable_sourced);
fails++;
} else if (callable_sourced < 1) {
printf(" %sFAIL%s [%s] callable-sourcing: 0 CALLS edges (fixture "
"produced no in-body call edge to attribute)\n",
RED, RST, lang_tag);
fails++;
}
/* 8. no-dangling */
int dangling = inv_count_dangling_edges(store, lp.project, "CALLS");
if (dangling != 0) {
printf(" %sFAIL%s [%s] no-dangling: %d dangling CALLS endpoint(s)\n",
RED, RST, lang_tag, dangling);
fails++;
}
rh_cleanup(&lp, store);
return fails ? 1 : 0;
}
/* ── Robustness helper: assert call RETURNS on malformed input ───────────────
*
* A truncated version of the fixture is passed through cbm_extract_file.
* has_error may be set (1) but the call must return non-NULL. If it returns
* NULL the extractor crashed or aborted on bad input -- that is a RED
* robustness bug. Returns 0 on PASS, 1 on FAIL.
*/
static int build_robustness(const char *lang_tag, const char *bad_src,
CBMLanguage lang, const char *file) {
const char *RED = tf_red();
const char *RST = tf_reset();
CBMFileResult *r = cbm_extract_file(bad_src, (int)strlen(bad_src),
lang, "t", file, 0, NULL, NULL);
if (!r) {
printf(" %sFAIL%s [%s] robustness: extractor returned NULL on malformed input\n",
RED, RST, lang_tag);
return 1;
}
cbm_free_result(r);
return 0;
}
/* ── Dockerfile ───────────────────────────────────────────────────────────────
* Idiomatic two-stage Dockerfile: a builder stage (FROM ... AS ...) followed by
* a runtime stage. ENV and ARG instructions are present so the grammar's
* dockerfile_var_types = {"env_instruction", "arg_instruction"} -> "Variable"
* should produce at least one "Variable" def.
*
* Dims asserted: 1-5 + R ("Variable").
* Dim 5 expected GREEN: ENV instruction should map to "Variable".
* RED would indicate env_instruction -> Variable extraction is broken.
* Dims 6-8 SKIPPED: no call_types in the spec; no pipeline.
* Expected GREEN: dims 1-5. Robustness should pass.
*/
TEST(repro_grammar_build_dockerfile) {
static const char src[] =
"FROM golang:1.22 AS builder\n"
"WORKDIR /app\n"
"ARG VERSION=0.8.1\n"
"COPY . .\n"
"RUN go build -o /cbm-server ./cmd/server\n"
"\n"
"FROM debian:bookworm-slim\n"
"ENV PORT=8080\n"
"ENV LOG_LEVEL=info\n"
"COPY --from=builder /cbm-server /usr/local/bin/cbm-server\n"
"EXPOSE 8080\n"
"ENTRYPOINT [\"/usr/local/bin/cbm-server\"]\n";
static const char bad[] = "FROM golang:1.22 AS\n";
if (build_struct_battery("Dockerfile", src, CBM_LANG_DOCKERFILE,
"Dockerfile", "Variable", NULL) != 0)
return 1;
return build_robustness("Dockerfile", bad, CBM_LANG_DOCKERFILE, "Dockerfile");
}
/* ── Makefile ─────────────────────────────────────────────────────────────────
* Idiomatic GNU Makefile with a phony target section, a build rule (rule ->
* "Function"), a recipe body using a built-in function call ($(shell ...) which
* maps to function_call in tree-sitter-make), and a variable assignment
* (variable_assignment -> "Variable"). The rule node is in makefile_func_types
* so "build" maps to "Function". The $(shell date) call maps to call_types.
*
* Dims asserted: 1-6 + R.
* Dim 5 expected GREEN: "Function" def for the "build" rule.
* RED would indicate rule->Function extraction is broken.
* Dim 6 expected GREEN: call to "shell" via $(shell ...) function_call.
* RED would indicate makefile function_call extraction is broken.
* Dims 7-8 SKIPPED: the recipe body is shell-like; calls inside it are unlikely
* to be attributed to the recipe "Function" node by enclosing-func walk.
* Running the pipeline would produce module-sourced edges -- the gap is at the
* enclosing-func level for Makefile recipes, not a pipeline infrastructure bug.
* Expected GREEN: dims 1-6. Robustness should pass.
*/
TEST(repro_grammar_build_makefile) {
static const char src[] =
"VERSION := 0.8.1\n"
"BINARY := cbm-server\n"
"\n"
".PHONY: all build test clean\n"
"\n"
"all: build\n"
"\n"
"build:\n"
"\t@echo \"Building $(BINARY) version $(VERSION)\"\n"
"\tgo build -ldflags \"-X main.version=$(VERSION)\" -o $(BINARY) ./cmd/server\n"
"\n"
"test:\n"
"\tgo test ./...\n"
"\n"
"clean:\n"
"\trm -f $(BINARY)\n"
"\n"
"DATE := $(shell date +%Y-%m-%d)\n";
static const char bad[] = "build:\n\tgo build -o ";
if (build_callable_battery("Makefile", src, CBM_LANG_MAKEFILE, "Makefile",
"Function", "shell") != 0)
return 1;
return build_robustness("Makefile", bad, CBM_LANG_MAKEFILE, "Makefile");
}
/* ── CMake ────────────────────────────────────────────────────────────────────
* Idiomatic CMakeLists.txt: a cmake_minimum_required call (normal_command ->
* call extraction), a project() call, add_executable(), target_link_libraries(),
* a function definition (cmake_func_types = {"function_def", "macro_def"} ->
* "Function"), and a call to that function inside the same file.
*
* Dims asserted: 1-6 + R.
* Dim 5 expected GREEN: "Function" def for the function_def "cbm_setup_target".
* RED would indicate function_def->Function extraction is broken.
* Dim 6 expected GREEN: call to "add_executable" via normal_command.
* RED would indicate CMake normal_command call extraction is broken.
* Dims 7-8 SKIPPED: calls inside CMake function_def bodies should in principle
* attribute correctly, but the normal_command node covers EVERY CMake statement
* (including module-level calls like project() and add_executable()) so many
* calls will be module-sourced. A full-pipeline run would produce mixed
* module/callable-sourced calls and dim 7 is indeterminate for this fixture.
* Expected GREEN: dims 1-6. Robustness should pass.
*/
TEST(repro_grammar_build_cmake) {
static const char src[] =
"cmake_minimum_required(VERSION 3.20)\n"
"project(cbm VERSION 0.8.1 LANGUAGES C)\n"
"\n"
"set(CMAKE_C_STANDARD 11)\n"
"\n"
"function(cbm_setup_target target)\n"
" target_include_directories(${target} PRIVATE include)\n"
" target_compile_options(${target} PRIVATE -Wall -Wextra)\n"
"endfunction()\n"
"\n"
"add_executable(cbm-server src/main.c src/server.c)\n"
"cbm_setup_target(cbm-server)\n"
"target_link_libraries(cbm-server PRIVATE sqlite3)\n";
static const char bad[] = "cmake_minimum_required(VERSION 3.20\n";
if (build_callable_battery("CMake", src, CBM_LANG_CMAKE, "CMakeLists.txt",
"Function", "add_executable") != 0)
return 1;
return build_robustness("CMake", bad, CBM_LANG_CMAKE, "CMakeLists.txt");
}
/* ── Meson ────────────────────────────────────────────────────────────────────
* Idiomatic meson.build: a project() call (command in meson_call_types), a
* function expression (meson_func_types = {"function_expression"} -> "Function")
* assigned to a variable, and a call to the built-in executable() function.
* Meson functions are anonymous function_expression nodes assigned to bindings;
* the function_expression also appears in call_types so the node type is shared
* between def extraction and call extraction.
*
* Dims asserted: 1-6 + R.
* Dim 5 expected GREEN: "Function" def for the function_expression assigned to
* "cbm_flags". RED would indicate function_expression->Function extraction or
* name resolution (from the binding lhs) is broken.
* Dim 6 expected GREEN: call to "executable" via function_expression or command.
* RED would indicate Meson call extraction is broken.
* Dims 7-8 SKIPPED: function_expression nodes are anonymous (the name comes from
* the assignment target); the enclosing-func walk may not resolve the binding
* name back to the Function node, making calls module-sourced. Pipeline skipped.
* Expected GREEN: dims 1-6. Robustness should pass.
*/
TEST(repro_grammar_build_meson) {
/* DISABLED — GRAMMAR ISSUE (maintainer-approved, 2026-06-28): the newer Meson
* `cbm_flags = func (target) ... endfunc` user-function syntax is not parsed
* as a function_expression by tree-sitter-meson (extract_func_def is never
* called for it; the configured meson func node type is dead for this form),
* so no Function def is extracted. A grammar/feature-coverage limitation, not
* a cbm bug. Original assertions below are preserved (unreachable). */
printf("%sSKIP%s grammar issue (meson func...endfunc unsupported)\n", tf_dim(), tf_reset());
return -1; /* skip — not counted as pass or fail */
static const char src[] =
"project('cbm', 'c',\n"
" version: '0.8.1',\n"
" default_options: ['c_std=c11'])\n"
"\n"
"cc = meson.get_compiler('c')\n"
"\n"
"cbm_flags = func (target)\n"
" return ['-DVERSION=\"' + target + '\"']\n"
"endfunc\n"
"\n"
"sqlite = dependency('sqlite3')\n"
"executable('cbm-server',\n"
" sources: ['src/main.c', 'src/server.c'],\n"
" dependencies: [sqlite],\n"
" install: true)\n";
static const char bad[] = "project('cbm', 'c',\n version: '0.8.1'";
if (build_callable_battery("Meson", src, CBM_LANG_MESON, "meson.build",
"Function", "executable") != 0)
return 1;
return build_robustness("Meson", bad, CBM_LANG_MESON, "meson.build");
}
/* ── GN (Generate Ninja) ──────────────────────────────────────────────────────
* Idiomatic BUILD.gn: a config() block and an executable() call
* (gn_call_types = {"call_expression"}). GN has no func_types in the spec so
* no "Function" def is minted. The call to "executable" should be extracted.
*
* Dims asserted: 1-4 + 6 + R.
* Dim 5 SKIPPED: no func_types or class_types in spec; no defs are extracted.
* Dim 6 expected GREEN: call to "executable" via call_expression.
* RED would indicate GN call_expression extraction is broken.
* Dims 7-8 SKIPPED: no func_types -> no Function anchor for callable-sourcing.
* Expected GREEN: dims 1-4 and 6. Robustness should pass.
*/
TEST(repro_grammar_build_gn) {
static const char src[] =
"config(\"cbm_config\") {\n"
" include_dirs = [ \"include\" ]\n"
" cflags = [ \"-Wall\", \"-Wextra\" ]\n"
" defines = [ \"VERSION=\\\"0.8.1\\\"\" ]\n"
"}\n"
"\n"
"executable(\"cbm-server\") {\n"
" sources = [\n"
" \"src/main.c\",\n"
" \"src/server.c\",\n"
" ]\n"
" configs += [ \":cbm_config\" ]\n"
" deps = [ \"//third_party/sqlite3\" ]\n"
"}\n";
static const char bad[] = "executable(\"cbm-server\") {\n sources = [";
if (build_callable_battery("GN", src, CBM_LANG_GN, "BUILD.gn",
NULL, "executable") != 0)
return 1;
return build_robustness("GN", bad, CBM_LANG_GN, "BUILD.gn");
}
/* ── Just ─────────────────────────────────────────────────────────────────────
* Idiomatic justfile with two recipes (just_func_types = {"recipe"} ->
* "Function") and a recipe dependency that the grammar encodes as a
* `dependency` node (just_call_types includes "dependency"). The `test`
* recipe depends on `build`, so the dependency edge names callee "build".
* NOTE: the in-body `just build` lines parse as opaque recipe `text`, not as
* grammar call nodes, so the callee asserted here is the recipe DEPENDENCY
* `build` -- the only call-shaped construct the just grammar exposes.
*
* Dims asserted: 1-8 (full battery).
* Dim 5 expected GREEN: "Function" def for "build" and "test" recipes.
* RED would indicate recipe->Function extraction is broken.
* Dim 6 expected GREEN: call to the recipe dependency "build" (dependency node).
* RED documents the just dependency-as-call extraction gap.
* Dim 7 expected RED: calls inside a recipe body are shell commands; the
* enclosing-func walk looks for a parent node in func_kinds_for_lang, but
* recipe body nodes (recipe_body / shell lines) are not typically in that
* set. Calls will be module-sourced.
* Dim 8 expected GREEN: no dangling CALLS endpoints.
* Robustness should pass.
*/
TEST(repro_grammar_build_just) {
static const char src[] =
"version := \"0.8.1\"\n"
"binary := \"cbm-server\"\n"
"\n"
"build:\n"
" go build -ldflags \"-X main.version={{version}}\" -o {{binary}} ./cmd/server\n"
"\n"
"test: build\n"
" go test ./...\n"
"\n"
"clean:\n"
" rm -f {{binary}}\n"
"\n"
"release version=version:\n"
" @echo \"Releasing {{version}}\"\n"
" just build\n"
" just test\n";
static const char bad[] = "build:\n go build -o ";
if (build_callable_battery("Just", src, CBM_LANG_JUST, "justfile",
"Function", "build") != 0)
return 1;
if (build_robustness("Just", bad, CBM_LANG_JUST, "justfile") != 0)
return 1;
return build_pipeline_battery("Just", "justfile", src);
}
/* ── K8s ──────────────────────────────────────────────────────────────────────
* Idiomatic Kubernetes manifest with a Deployment (apiVersion: apps/v1,
* kind: Deployment). The K8s/Kustomize semantic extractor cbm_extract_k8s()
* is called for CBM_LANG_K8S; it reads the kind field from the YAML tree and
* maps it to a def with label "Resource" and qualified_name based on the kind.
* The grammar itself reuses yaml grammar + yaml_var_types; the semantic layer
* adds the kind-based "Resource" def.
*
* Dims asserted: 1-5 + R ("Resource" for the Deployment kind).
* Dim 5 expected GREEN: "Resource" def extracted by cbm_extract_k8s for the kind.
* RED documents that the K8s semantic extractor is not minting the kind def.
* Dims 6-8 SKIPPED: no call_types in the K8s spec; no pipeline.
* Expected GREEN: dims 1-5. Robustness should pass.
*/
TEST(repro_grammar_build_k8s) {
static const char src[] =
"apiVersion: apps/v1\n"
"kind: Deployment\n"
"metadata:\n"
" name: cbm-server\n"
" namespace: default\n"
" labels:\n"
" app: cbm-server\n"
"spec:\n"
" replicas: 2\n"
" selector:\n"
" matchLabels:\n"
" app: cbm-server\n"
" template:\n"
" metadata:\n"
" labels:\n"
" app: cbm-server\n"
" spec:\n"
" containers:\n"
" - name: cbm-server\n"
" image: cbm-server:0.8.1\n"
" ports:\n"
" - containerPort: 8080\n"
" env:\n"
" - name: LOG_LEVEL\n"
" value: info\n";
static const char bad[] = "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n name:";
if (build_struct_battery("K8s", src, CBM_LANG_K8S, "deployment.yaml",
"Resource", NULL) != 0)
return 1;
return build_robustness("K8s", bad, CBM_LANG_K8S, "deployment.yaml");
}
/* ── Kustomize ────────────────────────────────────────────────────────────────
* Idiomatic kustomization.yaml: the Kustomize overlay tool's root file
* (kind: Kustomization). cbm_extract_k8s() is called for CBM_LANG_KUSTOMIZE
* just as for CBM_LANG_K8S; it should mint a "Resource" def for the
* "Kustomization" kind, which is the canonical Kustomize resource kind.
*
* Dims asserted: 1-5 + R ("Resource" for the Kustomization kind).
* Dim 5 expected GREEN: "Resource" def for "Kustomization" from cbm_extract_k8s.
* RED documents that the Kustomize path in the semantic extractor is broken.
* Dims 6-8 SKIPPED: no call_types in the Kustomize spec; no pipeline.
* Expected GREEN: dims 1-5. Robustness should pass.
*/
TEST(repro_grammar_build_kustomize) {
static const char src[] =
"apiVersion: kustomize.config.k8s.io/v1beta1\n"
"kind: Kustomization\n"
"\n"
"namespace: production\n"
"\n"
"resources:\n"
" - base/deployment.yaml\n"
" - base/service.yaml\n"
"\n"
"images:\n"
" - name: cbm-server\n"
" newTag: 0.8.1\n"
"\n"
"commonLabels:\n"
" environment: production\n"
" version: 0.8.1\n"
"\n"
"configMapGenerator:\n"
" - name: cbm-config\n"
" literals:\n"
" - LOG_LEVEL=info\n"
" - PORT=8080\n";
static const char bad[] = "apiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nresources:";
if (build_struct_battery("Kustomize", src, CBM_LANG_KUSTOMIZE,
"kustomization.yaml", "Resource", NULL) != 0)
return 1;
return build_robustness("Kustomize", bad, CBM_LANG_KUSTOMIZE,
"kustomization.yaml");
}
/* ── GoMod ────────────────────────────────────────────────────────────────────
* Idiomatic go.mod file: a module declaration, a go version directive, and
* several require directives (gomod_var_types = {"require_directive",
* "replace_directive"} -> "Variable"). Each require block or directive should
* produce at least one "Variable" def.
*
* Dims asserted: 1-5 + R ("Variable" from require_directive).
* Dim 5 expected GREEN: "Variable" def for the require directives.
* RED documents that require_directive->Variable extraction is broken.
* Dims 6-8 SKIPPED: no call_types or func_types in spec.
* Expected GREEN: dims 1-5. Robustness should pass.
*/
TEST(repro_grammar_build_gomod) {
static const char src[] =
"module github.com/DeusData/codebase-memory-mcp\n"
"\n"
"go 1.22\n"
"\n"
"require (\n"
" github.com/mattn/go-sqlite3 v1.14.22\n"
" github.com/mark3labs/mcp-go v0.17.0\n"
" golang.org/x/sync v0.7.0\n"
")\n"
"\n"
"require (\n"
" github.com/google/uuid v1.6.0\n"
" github.com/stretchr/testify v1.9.0\n"
")\n";
static const char bad[] = "module github.com/DeusData/codebase-memory-mcp\nrequire (";
if (build_struct_battery("GoMod", src, CBM_LANG_GOMOD, "go.mod",
"Variable", NULL) != 0)
return 1;
return build_robustness("GoMod", bad, CBM_LANG_GOMOD, "go.mod");
}
/* ── Requirements (pip) ───────────────────────────────────────────────────────
* Idiomatic Python requirements.txt with version pins and a URL requirement.
* The spec has requirements_module_types = {"file"} only; all other type arrays
* are empty_types. No defs or calls are extracted from the grammar tree.
*
* Dims asserted: 1-4 + R.
* Dim 5 SKIPPED: no func/class/var types in spec; no labelled defs expected.
* Dims 6-8 SKIPPED: no call_types in spec.
* Expected GREEN: dims 1-4. extract-clean RED would indicate the requirements
* grammar is broken on standard version-pinned lines.
* Robustness should pass.
*/
TEST(repro_grammar_build_requirements) {
static const char src[] =
"# Core dependencies\n"
"requests==2.31.0\n"
"fastapi>=0.100.0,<1.0.0\n"
"uvicorn[standard]==0.23.2\n"
"pydantic>=2.0.0\n"
"sqlalchemy==2.0.23\n"
"\n"
"# Dev dependencies\n"
"pytest==7.4.3\n"
"mypy==1.7.0\n"
"ruff==0.1.6\n"
"\n"
"# URL requirement\n"
"cbm-client @ git+https://github.com/DeusData/cbm-client.git@v0.8.1\n";
static const char bad[] = "requests==2.31.0\nbroken>=";
if (build_base_battery("Requirements", src, CBM_LANG_REQUIREMENTS,
"requirements.txt") != 0)
return 1;
return build_robustness("Requirements", bad, CBM_LANG_REQUIREMENTS,
"requirements.txt");
}
/* ── .gitignore ───────────────────────────────────────────────────────────────
* Idiomatic .gitignore file with patterns for a Go project. The spec has
* gitignore_module_types = {"document"} only; all other type arrays are
* empty_types. No defs or calls are extracted from the grammar tree.
*
* Dims asserted: 1-4 + R.
* Dim 5 SKIPPED: no func/class/var types in spec.
* Dims 6-8 SKIPPED: no call_types.
* Expected GREEN: dims 1-4. extract-clean RED would indicate the gitignore
* grammar is broken on standard pattern lines.
* Robustness should pass.
*/
TEST(repro_grammar_build_gitignore) {
static const char src[] =
"# Compiled binaries\n"
"cbm-server\n"
"*.exe\n"
"*.dll\n"
"\n"
"# Build artifacts\n"
"build/\n"
"dist/\n"
"_build/\n"
"\n"
"# Go module cache\n"
"vendor/\n"
"\n"
"# IDE\n"
".idea/\n"
".vscode/\n"
"*.swp\n"
"\n"
"# Test coverage\n"
"coverage.out\n"
"*.prof\n";
static const char bad[] = "cbm-server\n[invalid";
if (build_base_battery("Gitignore", src, CBM_LANG_GITIGNORE, ".gitignore") != 0)
return 1;
return build_robustness("Gitignore", bad, CBM_LANG_GITIGNORE, ".gitignore");
}
/* ── .gitattributes ───────────────────────────────────────────────────────────
* Idiomatic .gitattributes file with line-ending and language attribution rules.
* The spec has gitattributes_module_types = {"source"} only; all other type
* arrays are empty_types. No defs or calls are extracted.
*
* Dims asserted: 1-4 + R.
* Dim 5 SKIPPED: no func/class/var types in spec.
* Dims 6-8 SKIPPED: no call_types.
* Expected GREEN: dims 1-4. extract-clean RED would indicate the gitattributes
* grammar is broken on standard attribute lines.
* Robustness should pass.
*/
TEST(repro_grammar_build_gitattributes) {
static const char src[] =
"# Normalise line endings\n"
"* text=auto eol=lf\n"
"\n"
"# Go source files\n"
"*.go text eol=lf\n"
"\n"
"# C source files (vendored grammars)\n"
"*.c text eol=lf\n"
"*.h text eol=lf\n"
"\n"
"# Binary files\n"
"*.db binary\n"
"*.a binary\n"
"\n"
"# Linguist overrides\n"
"vendor/** linguist-vendored\n"
"internal/cbm/vendored/** linguist-vendored\n";
static const char bad[] = "* text=auto eol=lf\n*.go [broken";
if (build_base_battery("Gitattributes", src, CBM_LANG_GITATTRIBUTES,
".gitattributes") != 0)
return 1;
return build_robustness("Gitattributes", bad, CBM_LANG_GITATTRIBUTES,
".gitattributes");
}
/* ── SSH Config ───────────────────────────────────────────────────────────────
* Idiomatic ~/.ssh/config file with two Host stanzas. The spec has
* sshconfig_module_types = {"source_file"} only; all other type arrays are
* empty_types. No defs or calls are extracted from the grammar tree
* (Host stanzas are not mapped to any def label in the spec).
*
* Dims asserted: 1-4 + R.
* Dim 5 SKIPPED: no func/class/var types in spec; Host stanzas are not labelled.
* Dims 6-8 SKIPPED: no call_types.
* Expected GREEN: dims 1-4. extract-clean RED would indicate the ssh_config
* grammar is broken on standard Host/IdentityFile stanza syntax.
* Robustness should pass.
*/
TEST(repro_grammar_build_sshconfig) {
static const char src[] =
"Host github.com\n"
" HostName github.com\n"
" User git\n"
" IdentityFile ~/.ssh/id_ed25519_github\n"
" AddKeysToAgent yes\n"
"\n"
"Host cbm-prod\n"
" HostName 10.0.0.42\n"
" User deploy\n"
" IdentityFile ~/.ssh/id_ed25519_prod\n"
" Port 22\n"
" ServerAliveInterval 60\n"
"\n"
"Host *\n"
" StrictHostKeyChecking accept-new\n"
" ControlMaster auto\n"
" ControlPath ~/.ssh/cm-%r@%h:%p\n";
static const char bad[] = "Host github.com\n HostName github.com\n User git\n IdentityFile";
if (build_base_battery("SSHConfig", src, CBM_LANG_SSHCONFIG, "config") != 0)
return 1;
return build_robustness("SSHConfig", bad, CBM_LANG_SSHCONFIG, "config");
}
/* ── BitBake ──────────────────────────────────────────────────────────────────
* Idiomatic BitBake recipe (.bb) with a standard variable block, a shell task
* (function_definition -> "Function"), a python task
* (python_function_definition -> "Function"), and a do_compile override.
* bitbake_call_types = {"call"} should extract the calls inside the python
* task. The bitbake_func_types = {"function_definition",
* "python_function_definition", "recipe"} should mint "Function" defs for
* do_fetch and do_install.
*
* Dims asserted: 1-8 (full battery).
* Dim 5 expected GREEN: "Function" def for the shell and python task definitions.
* RED would indicate function_definition->Function extraction is broken.
* Dim 6 expected GREEN: call extraction inside the python task.
* RED documents the call node extraction gap for BitBake python blocks.
* Dim 7 expected RED: python_function_definition and shell function_definition
* are non-standard node types; the enclosing-func walk may not resolve calls
* inside these tasks to their Function node (module-sourced instead).
* Dim 8 expected GREEN: no dangling CALLS endpoints.
* Robustness should pass.
*/
TEST(repro_grammar_build_bitbake) {
/* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): BitBake (Yocto
* recipe DSL) produces no in-body CALLS edge for the fixture's task/function
* body — a callee/extraction gap in a niche build DSL. Deferred for now; not a
* mainstream-language bug. Original assertions below are preserved
* (unreachable) for re-enable. */
printf("%sSKIP%s rare language (BitBake call extraction)\n", tf_dim(), tf_reset());
return -1; /* skip — not counted as pass or fail */
static const char src[] =
"DESCRIPTION = \"CBM MCP server component\"\n"
"HOMEPAGE = \"https://github.com/DeusData/codebase-memory-mcp\"\n"
"LICENSE = \"MIT\"\n"
"PV = \"0.8.1\"\n"
"\n"
"SRC_URI = \"git://github.com/DeusData/codebase-memory-mcp.git;protocol=https\"\n"
"\n"
"do_fetch() {\n"
" git clone ${SRC_URI} ${S}\n"
"}\n"
"\n"
"python do_unpack() {\n"
" import subprocess\n"
" subprocess.run(['git', 'checkout', d.getVar('PV')])\n"
" bb.note('Unpacked version ' + d.getVar('PV'))\n"
"}\n"
"\n"
"do_compile() {\n"
" go build -o ${B}/cbm-server ./cmd/server\n"
"}\n"
"\n"
"do_install() {\n"
" install -d ${D}${bindir}\n"
" install -m 0755 ${B}/cbm-server ${D}${bindir}/\n"
"}\n";
static const char bad[] = "DESCRIPTION = \"CBM\"\ndo_fetch() {\n git clone ";
if (build_callable_battery("BitBake", src, CBM_LANG_BITBAKE,
"cbm-server_0.8.1.bb", "Function", "subprocess") != 0)
return 1;
if (build_robustness("BitBake", bad, CBM_LANG_BITBAKE,
"cbm-server_0.8.1.bb") != 0)
return 1;
return build_pipeline_battery("BitBake", "cbm-server_0.8.1.bb", src);
}
/* ── Puppet ───────────────────────────────────────────────────────────────────
* Idiomatic Puppet manifest: a class definition (puppet_class_types =
* {"class_definition", ...} -> "Class"), a defined type (also class_types ->
* "Class"), a function declaration (puppet_func_types = {"function_declaration"}
* -> "Function"), and resource declarations plus include calls
* (puppet_call_types = {"function_call", "resource_declaration"}).
*
* Dims asserted: 1-8 (full battery).
* Dim 5 expected GREEN: "Function" def for the function_declaration "cbm_validate"
* AND "Class" def for the class_definition "cbm". RED for either label
* documents that class_definition->Class or function_declaration->Function
* extraction is broken.
* Dim 6 expected GREEN: call to "include" via function_call node.
* RED documents the Puppet function_call extraction gap.
* Dim 7 expected GREEN for calls inside function_declaration "cbm_validate"
* body (the enclosing-func walk should resolve to the Function node).
* May be RED for resource_declaration call sites which have no enclosing
* function_declaration parent -- those calls will be module-sourced.
* Dim 8 expected GREEN: no dangling CALLS endpoints.
* Robustness should pass.
*/
TEST(repro_grammar_build_puppet) {
/* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): Puppet (config
* management DSL) sources its in-body call to the Module (enclosing-func gap
* for Puppet's define/function node), and the grammar's call/func modelling is
* niche. Deferred for now; not a mainstream-language bug. Original assertions
* below are preserved (unreachable) for re-enable. */
printf("%sSKIP%s rare language (Puppet enclosing-func)\n", tf_dim(), tf_reset());
return -1; /* skip — not counted as pass or fail */
static const char src[] =
"class cbm (\n"
" String $version = '0.8.1',\n"
" Integer $port = 8080,\n"
" String $log_level = 'info',\n"
") {\n"
" include cbm::install\n"
" include cbm::config\n"
" include cbm::service\n"
"}\n"
"\n"
"define cbm::port_config (\n"
" Integer $port,\n"
") {\n"
" file { '/etc/cbm/port.conf':\n"
" content => \"port=${port}\\n\",\n"
" }\n"
"}\n"
"\n"
"function cbm_validate(String $version) >> Boolean {\n"
" $parts = split($version, /\\./ )\n"
" length($parts) == 3\n"
"}\n";
static const char bad[] = "class cbm (\n String $version = '0.8.1',\n) {\n include";
if (build_callable_battery("Puppet", src, CBM_LANG_PUPPET, "cbm.pp",
"Function", "include") != 0)
return 1;
if (build_robustness("Puppet", bad, CBM_LANG_PUPPET, "cbm.pp") != 0)
return 1;
return build_pipeline_battery("Puppet", "cbm.pp", src);
}
/* ── Suite ───────────────────────────────────────────────────────────────────── */
SUITE(repro_grammar_build) {
RUN_TEST(repro_grammar_build_dockerfile);
RUN_TEST(repro_grammar_build_makefile);
RUN_TEST(repro_grammar_build_cmake);
RUN_TEST(repro_grammar_build_meson);
RUN_TEST(repro_grammar_build_gn);
RUN_TEST(repro_grammar_build_just);
RUN_TEST(repro_grammar_build_k8s);
RUN_TEST(repro_grammar_build_kustomize);
RUN_TEST(repro_grammar_build_gomod);
RUN_TEST(repro_grammar_build_requirements);
RUN_TEST(repro_grammar_build_gitignore);
RUN_TEST(repro_grammar_build_gitattributes);
RUN_TEST(repro_grammar_build_sshconfig);
RUN_TEST(repro_grammar_build_bitbake);
RUN_TEST(repro_grammar_build_puppet);
}