Files
deusdata--codebase-memory-mcp/tests/test_grammar_probe_f.c
T
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

1653 lines
60 KiB
C

/*
* test_grammar_probe_f.c — IaC/build node/edge probe.
*
* SCOPE
* ─────
* Probes 19 IaC/config-as-code and build grammars:
* IaC/config: hcl, k8s, kustomize, nix, nickel, pkl, ron, jsonnet, kdl,
* hyprlang, devicetree
* Build: cmake, makefile, meson, gn, just, bitbake, kconfig, gomod
*
* DIMENSIONS PER GRAMMAR
* ──────────────────────
* • NODE creation — resource/target/recipe/config-entry blocks reach the graph
* • IMPORTS edges — include/import/`use` where the pipeline models it
* • INFRA_MAPS — HCL module.source → Route edge (parallel path only; probed
* in the single-path here as an expected RED)
* • DEPENDS_ON — gomod require → dependency node
*
* COLOUR LEGEND
* ─────────────
* green = guard: the pipeline already produces the result; a failure is a regression.
* red = bug reproduction: pipeline does NOT yet produce the expected node/edge;
* the test FAILS until fixed. Brief inline comment names the root cause.
*
* Not registered in test_main.c — sibling agent owns that file.
*
* SUITE(grammar_probe_f)
*/
#include "../src/foundation/compat.h"
#include "test_framework.h"
#include "test_helpers.h"
#include "cbm.h"
#include <mcp/mcp.h>
#include <store/store.h>
#include <pipeline/pipeline.h>
#include <foundation/log.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
/* ══════════════════════════════════════════════════════════════════
* Harness — mirrors test_grammar_probe_a.c (prefix "gpf_").
* ══════════════════════════════════════════════════════════════════ */
typedef struct {
char tmpdir[256];
char dbpath[512];
char *project;
cbm_mcp_server_t *srv;
} GpfProj;
typedef struct {
const char *name;
const char *content;
} GpfFile;
static void gpf_to_fwd_slashes(char *p) {
for (; *p; p++) {
if (*p == '\\') *p = '/';
}
}
static cbm_store_t *gpf_open_indexed(GpfProj *lp) {
lp->project = cbm_project_name_from_path(lp->tmpdir);
if (!lp->project) return NULL;
const char *home = getenv("HOME");
if (!home) home = "/tmp";
char cache_dir[512];
snprintf(cache_dir, sizeof(cache_dir), "%s/.cache/codebase-memory-mcp", home);
cbm_mkdir(cache_dir);
snprintf(lp->dbpath, sizeof(lp->dbpath), "%s/%s.db", cache_dir, lp->project);
unlink(lp->dbpath);
lp->srv = cbm_mcp_server_new(NULL);
if (!lp->srv) return NULL;
char args[700];
snprintf(args, sizeof(args), "{\"repo_path\":\"%s\"}", lp->tmpdir);
char *resp = cbm_mcp_handle_tool(lp->srv, "index_repository", args);
if (resp) free(resp);
return cbm_store_open_path(lp->dbpath);
}
static cbm_store_t *gpf_index_files(GpfProj *lp, const GpfFile *files, int nfiles) {
memset(lp, 0, sizeof(*lp));
snprintf(lp->tmpdir, sizeof(lp->tmpdir), "/tmp/cbm_gpf_XXXXXX");
if (!cbm_mkdtemp(lp->tmpdir)) return NULL;
gpf_to_fwd_slashes(lp->tmpdir);
for (int i = 0; i < nfiles; i++) {
char path[700];
snprintf(path, sizeof(path), "%s/%s", lp->tmpdir, files[i].name);
char *slash = strrchr(path, '/');
if (slash && slash > path + strlen(lp->tmpdir)) {
*slash = '\0';
cbm_mkdir_p(path, 0755);
*slash = '/';
}
FILE *f = fopen(path, "wb");
if (!f) return NULL;
fputs(files[i].content, f);
fclose(f);
}
return gpf_open_indexed(lp);
}
static void gpf_cleanup(GpfProj *lp, cbm_store_t *store) {
if (store) cbm_store_close(store);
if (lp->srv) { cbm_mcp_server_free(lp->srv); lp->srv = NULL; }
free(lp->project);
lp->project = NULL;
th_rmtree(lp->tmpdir);
unlink(lp->dbpath);
char wal[600], shm[600];
snprintf(wal, sizeof(wal), "%s-wal", lp->dbpath);
unlink(wal);
snprintf(shm, sizeof(shm), "%s-shm", lp->dbpath);
unlink(shm);
}
/* ── Node-count helpers ──────────────────────────────────────────── */
static int gpf_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;
}
/* Metrics bundled per index pass. */
typedef struct {
int ok;
int total_nodes;
int functions; /* Function label */
int modules; /* Module label */
int classes; /* Class label */
int resources; /* Resource label (k8s) */
int imports; /* IMPORTS edges */
int depends_on; /* DEPENDS_ON edges */
int infra_maps; /* INFRA_MAPS edges */
} GpfMetrics;
static GpfMetrics gpf_metrics_files(const GpfFile *files, int nfiles) {
GpfProj lp;
cbm_store_t *store = gpf_index_files(&lp, files, nfiles);
GpfMetrics m = {0};
if (store) {
m.ok = 1;
m.total_nodes = cbm_store_count_nodes(store, lp.project);
m.functions = gpf_count_label(store, lp.project, "Function");
m.modules = gpf_count_label(store, lp.project, "Module");
m.classes = gpf_count_label(store, lp.project, "Class");
m.resources = gpf_count_label(store, lp.project, "Resource");
m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS");
m.depends_on = cbm_store_count_edges_by_type(store, lp.project, "DEPENDS_ON");
m.infra_maps = cbm_store_count_edges_by_type(store, lp.project, "INFRA_MAPS");
}
gpf_cleanup(&lp, store);
return m;
}
static GpfMetrics gpf_metrics(const char *filename, const char *content) {
GpfFile f = {filename, content};
return gpf_metrics_files(&f, 1);
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 1 — HCL / Terraform (.hcl, .tf)
*
* Labels histogram: Class:1, Module:1
* The extractor models HCL as a single Module node plus a Class node for
* the file-level scope. Individual `resource` / `module` blocks are NOT
* yet extracted as separate nodes (grammar-only path).
* INFRA_MAPS is produced only by the parallel-path pipeline (>50 files);
* single-file fixture tests it as RED.
* ══════════════════════════════════════════════════════════════════ */
/* HCL: minimal resource block → at least 1 node (Module) and 1 Class node. */
TEST(probe_hcl_resource_nodes) {
GpfMetrics m = gpf_metrics("main.tf",
"resource \"aws_s3_bucket\" \"my_bucket\" {\n"
" bucket = \"my-tf-test-bucket\"\n"
" acl = \"private\"\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: at minimum a Module node is created for an HCL file. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* HCL: Class node present alongside Module (label histogram: Class:1,Module:1). */
TEST(probe_hcl_class_node) {
GpfMetrics m = gpf_metrics("infra.hcl",
"resource \"aws_instance\" \"web\" {\n"
" ami = \"ami-0c55b159cbfafe1f0\"\n"
" instance_type = \"t2.micro\"\n"
"}\n"
"\n"
"resource \"aws_instance\" \"db\" {\n"
" ami = \"ami-0c55b159cbfafe1f0\"\n"
" instance_type = \"t2.small\"\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: histogram confirms Class:1,Module:1 for any non-empty HCL file. */
ASSERT_TRUE(m.classes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* HCL: module block with `source` → separate resource-like node.
* RED: grammar-only HCL extractor produces only Class+Module (one per file);
* individual resource/module blocks are not broken out as distinct nodes. */
TEST(probe_hcl_module_block_node) {
GpfMetrics m = gpf_metrics("modules.tf",
"module \"vpc\" {\n"
" source = \"terraform-aws-modules/vpc/aws\"\n"
" version = \"3.14.0\"\n"
" cidr = \"10.0.0.0/16\"\n"
"}\n"
"\n"
"module \"eks\" {\n"
" source = \"terraform-aws-modules/eks/aws\"\n"
" version = \"18.26.6\"\n"
" cluster_name = \"my-eks-cluster\"\n"
"}\n");
ASSERT_TRUE(m.ok);
/* RED: individual module blocks not extracted as separate Resource/Module nodes;
* only the file-level Class+Module pair is produced. */
ASSERT_TRUE(m.total_nodes >= 3); /* expected RED — no per-block node extraction */
PASS();
}
/* HCL: INFRA_MAPS edge (module.source URL → Route node).
* RED: INFRA_MAPS is produced only by the parallel-path pipeline (>50 files);
* single-file fixture never crosses the threshold. */
TEST(probe_hcl_infra_maps_edge) {
GpfMetrics m = gpf_metrics("sched.tf",
"resource \"google_cloud_scheduler_job\" \"cron\" {\n"
" name = \"cron-job\"\n"
" schedule = \"0 * * * *\"\n"
" http_target {\n"
" uri = \"https://my-service.example.com/run\"\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
/* RED: INFRA_MAPS requires parallel-path (>50 files) — not triggered here. */
ASSERT_TRUE(m.infra_maps >= 1); /* expected RED — parallel path only */
PASS();
}
/* HCL: two-file fixture (.tf + .hcl) → still no cross-file IMPORTS edge
* because HCL/Terraform uses a directory-level evaluation model, not imports. */
TEST(probe_hcl_no_spurious_imports) {
static const GpfFile files[] = {
{"vars.hcl",
"variable \"region\" {\n"
" default = \"us-east-1\"\n"
"}\n"},
{"main.tf",
"resource \"aws_s3_bucket\" \"b\" {\n"
" bucket = \"test\"\n"
"}\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* GREEN: HCL has no import syntax — pipeline should emit 0 IMPORTS edges. */
ASSERT_TRUE(m.imports == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 2 — Kubernetes manifests (.yaml with apiVersion:)
*
* Labels histogram: Module:1 (generic YAML pass); Resource node is emitted
* by pass_k8s.c (handle_k8s_manifest → cbm_extract_file CBM_LANG_K8S).
* ══════════════════════════════════════════════════════════════════ */
/* K8s: Pod manifest → Resource node (kind=Pod, name=nginx-pod). */
TEST(probe_k8s_pod_resource_node) {
GpfMetrics m = gpf_metrics("pod.yaml",
"apiVersion: v1\n"
"kind: Pod\n"
"metadata:\n"
" name: nginx-pod\n"
" namespace: default\n"
"spec:\n"
" containers:\n"
" - name: nginx\n"
" image: nginx:1.21\n");
ASSERT_TRUE(m.ok);
/* GREEN: pass_k8s emits a Resource node for any k8s manifest with apiVersion. */
ASSERT_TRUE(m.resources >= 1);
PASS();
}
/* K8s: Deployment manifest → Resource node. */
TEST(probe_k8s_deployment_resource_node) {
GpfMetrics m = gpf_metrics("deploy.yaml",
"apiVersion: apps/v1\n"
"kind: Deployment\n"
"metadata:\n"
" name: my-app\n"
"spec:\n"
" replicas: 3\n"
" selector:\n"
" matchLabels:\n"
" app: my-app\n"
" template:\n"
" metadata:\n"
" labels:\n"
" app: my-app\n"
" spec:\n"
" containers:\n"
" - name: app\n"
" image: my-app:latest\n");
ASSERT_TRUE(m.ok);
/* GREEN: Deployment kind → Resource node via pass_k8s. */
ASSERT_TRUE(m.resources >= 1);
PASS();
}
/* K8s: two manifests in one directory → 2 Resource nodes (one per file). */
TEST(probe_k8s_two_manifests) {
static const GpfFile files[] = {
{"service.yaml",
"apiVersion: v1\n"
"kind: Service\n"
"metadata:\n"
" name: my-svc\n"
"spec:\n"
" selector:\n"
" app: my-app\n"
" ports:\n"
" - port: 80\n"},
{"configmap.yaml",
"apiVersion: v1\n"
"kind: ConfigMap\n"
"metadata:\n"
" name: app-config\n"
"data:\n"
" key: value\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* GREEN: each manifest file produces its own Resource node. */
ASSERT_TRUE(m.resources >= 2);
PASS();
}
/* K8s: cross-manifest reference via INFRA_MAPS.
* RED: the single-path pipeline does not emit INFRA_MAPS edges for k8s refs;
* pass_k8s only emits Resource nodes (no ref-resolution between manifests). */
TEST(probe_k8s_infra_maps_edge) {
static const GpfFile files[] = {
{"deploy.yaml",
"apiVersion: apps/v1\n"
"kind: Deployment\n"
"metadata:\n"
" name: frontend\n"
"spec:\n"
" template:\n"
" spec:\n"
" containers:\n"
" - name: frontend\n"
" image: frontend:latest\n"},
{"service.yaml",
"apiVersion: v1\n"
"kind: Service\n"
"metadata:\n"
" name: frontend-svc\n"
"spec:\n"
" selector:\n"
" app: frontend\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: inter-manifest INFRA_MAPS edges not modeled by the current k8s pass. */
ASSERT_TRUE(m.infra_maps >= 1); /* expected RED — no k8s selector → INFRA_MAPS */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 3 — Kustomize (kustomization.yaml)
*
* Labels histogram: Module:1
* pass_k8s.c handle_kustomize: emits a Module node + IMPORTS edges for each
* resources/bases/patches entry that resolves to an existing graph node.
* ══════════════════════════════════════════════════════════════════ */
/* Kustomize: overlay file → Module node. */
TEST(probe_kustomize_module_node) {
GpfMetrics m = gpf_metrics("kustomization.yaml",
"apiVersion: kustomize.config.k8s.io/v1beta1\n"
"kind: Kustomization\n"
"resources:\n"
" - deploy.yaml\n"
" - service.yaml\n");
ASSERT_TRUE(m.ok);
/* GREEN: handle_kustomize always emits a Module node for the overlay file. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Kustomize: overlay + two resource manifests → IMPORTS edges.
* The resolver looks for nodes whose QN matches the resource paths.
* Each resolved entry produces an IMPORTS edge (Module → Resource node).
* RED: resources are YAML files that pass_k8s processes as k8s manifests only
* when apiVersion is present; without it they are plain YAML with no node
* in the graph, so the IMPORTS edge can't be created. */
TEST(probe_kustomize_imports_edges) {
static const GpfFile files[] = {
{"kustomization.yaml",
"apiVersion: kustomize.config.k8s.io/v1beta1\n"
"kind: Kustomization\n"
"resources:\n"
" - deploy.yaml\n"
" - service.yaml\n"},
{"deploy.yaml",
"apiVersion: apps/v1\n"
"kind: Deployment\n"
"metadata:\n"
" name: myapp\n"
"spec:\n"
" replicas: 1\n"
" template:\n"
" spec:\n"
" containers:\n"
" - name: app\n"
" image: myapp:v1\n"},
{"service.yaml",
"apiVersion: v1\n"
"kind: Service\n"
"metadata:\n"
" name: myapp-svc\n"
"spec:\n"
" selector:\n"
" app: myapp\n"
" ports:\n"
" - port: 80\n"}
};
GpfMetrics m = gpf_metrics_files(files, 3);
ASSERT_TRUE(m.ok);
/* Kustomize Module node is always emitted. */
ASSERT_TRUE(m.modules >= 1);
/* RED: IMPORTS edge from kustomize Module → Resource nodes requires the
* resource file to already be in the graph as a QN-matched node;
* the ordering of pipeline passes may leave resource nodes absent
* when the kustomize pass runs, preventing edge creation. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — resource QN resolution fails */
PASS();
}
/* Kustomize: base overlay with patches entry → Module node (no crash). */
TEST(probe_kustomize_with_patches) {
GpfMetrics m = gpf_metrics("kustomization.yaml",
"apiVersion: kustomize.config.k8s.io/v1beta1\n"
"kind: Kustomization\n"
"bases:\n"
" - ../base\n"
"patches:\n"
" - path: replica-patch.yaml\n");
ASSERT_TRUE(m.ok);
/* GREEN: handle_kustomize emits Module node regardless of patches content. */
ASSERT_TRUE(m.modules >= 1);
ASSERT_TRUE(m.total_nodes >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 4 — Nix (.nix)
*
* Labels histogram: Module:1
* Nix is a purely functional language; the grammar-only extractor produces
* a single Module node per file. Nix `import` expressions (e.g.
* `import ./other.nix`) are textual — the pipeline does not yet resolve
* them into IMPORTS graph edges.
* ══════════════════════════════════════════════════════════════════ */
/* Nix: attribute set with derivation-like attrs → at least 1 node. */
TEST(probe_nix_attrset_node) {
GpfMetrics m = gpf_metrics("default.nix",
"{ pkgs ? import <nixpkgs> {} }:\n"
"\n"
"pkgs.stdenv.mkDerivation {\n"
" pname = \"mypackage\";\n"
" version = \"1.0.0\";\n"
" src = ./src;\n"
" buildInputs = [ pkgs.gcc pkgs.make ];\n"
" buildPhase = \"make\";\n"
" installPhase = \"make install\";\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty Nix file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Nix: let-binding expression — multiple top-level attrs. */
TEST(probe_nix_let_binding) {
GpfMetrics m = gpf_metrics("lib.nix",
"let\n"
" double = x: x * 2;\n"
" square = x: x * x;\n"
" cube = x: x * x * x;\n"
"in {\n"
" inherit double square cube;\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: file-level Module node created. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Nix: `import` in two-file fixture → IMPORTS edge.
* RED: Nix import expressions are not resolved into IMPORTS graph edges. */
TEST(probe_nix_import_edge) {
static const GpfFile files[] = {
{"utils.nix",
"{ double = x: x * 2; }\n"},
{"default.nix",
"let utils = import ./utils.nix;\n"
"in { result = utils.double 21; }\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Nix `import` not resolved into IMPORTS edges by the grammar-only pipeline. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Nix import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 5 — Nickel (.ncl)
*
* Labels histogram: Module:1
* Nickel is a configuration language; the extractor produces a single Module
* node per file. Import with `import "path"` is syntactically supported but
* not yet resolved into IMPORTS edges.
* ══════════════════════════════════════════════════════════════════ */
/* Nickel: record literal → Module node (no crash). */
TEST(probe_nickel_record_node) {
GpfMetrics m = gpf_metrics("config.ncl",
"{\n"
" server = {\n"
" host = \"localhost\",\n"
" port = 8080,\n"
" },\n"
" database = {\n"
" name = \"mydb\",\n"
" max_connections = 10,\n"
" },\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty Nickel file produces a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Nickel: let-binding with function value. */
TEST(probe_nickel_let_fn) {
GpfMetrics m = gpf_metrics("lib.ncl",
"let double = fun x => x * 2 in\n"
"let square = fun x => x * x in\n"
"{ double, square }\n");
ASSERT_TRUE(m.ok);
/* GREEN: file-level Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Nickel: `import` in two-file fixture → IMPORTS edge.
* RED: Nickel import expressions not resolved by the pipeline. */
TEST(probe_nickel_import_edge) {
static const GpfFile files[] = {
{"utils.ncl", "{ double = fun x => x * 2 }\n"},
{"main.ncl",
"let utils = import \"utils.ncl\" in\n"
"{ result = utils.double 21 }\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Nickel `import` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Nickel import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 6 — Pkl (.pkl)
*
* Labels histogram: Module:1
* Pkl is Apple's configuration language. `amends`/`import` for cross-file
* references — not yet resolved by the pipeline.
* ══════════════════════════════════════════════════════════════════ */
/* Pkl: property assignments → Module node. */
TEST(probe_pkl_properties_node) {
GpfMetrics m = gpf_metrics("config.pkl",
"host = \"localhost\"\n"
"port = 8080\n"
"debug = false\n"
"maxConnections = 10\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty Pkl file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Pkl: class-like object definition. */
TEST(probe_pkl_object_block) {
GpfMetrics m = gpf_metrics("server.pkl",
"server {\n"
" host = \"0.0.0.0\"\n"
" port = 443\n"
" tls {\n"
" enabled = true\n"
" cert = \"/etc/ssl/cert.pem\"\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Pkl: `import` in two-file fixture → IMPORTS edge.
* RED: Pkl import/amends not resolved into IMPORTS edges. */
TEST(probe_pkl_import_edge) {
static const GpfFile files[] = {
{"base.pkl",
"host = \"localhost\"\n"
"port = 8080\n"},
{"prod.pkl",
"amends \"base.pkl\"\n"
"\n"
"port = 443\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Pkl `amends`/`import` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Pkl import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 7 — RON (.ron)
*
* Labels histogram: Module:1
* RON (Rusty Object Notation) is a data serialization format; the extractor
* produces a Module node per file. No import mechanism exists in RON.
* ══════════════════════════════════════════════════════════════════ */
/* RON: struct-like record → Module node. */
TEST(probe_ron_record_node) {
GpfMetrics m = gpf_metrics("config.ron",
"(\n"
" host: \"localhost\",\n"
" port: 8080,\n"
" debug: false,\n"
" tags: [\"api\", \"v2\"],\n"
")\n");
ASSERT_TRUE(m.ok);
/* GREEN: RON file produces at least 1 node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* RON: named struct. */
TEST(probe_ron_named_struct) {
GpfMetrics m = gpf_metrics("scene.ron",
"Scene(\n"
" entities: [\n"
" Entity(id: 1, name: \"player\"),\n"
" Entity(id: 2, name: \"enemy\"),\n"
" ],\n"
")\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node for the RON file. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* RON: no import mechanism → 0 IMPORTS edges expected. */
TEST(probe_ron_no_imports) {
GpfMetrics m = gpf_metrics("data.ron",
"[\n"
" (key: \"a\", value: 1),\n"
" (key: \"b\", value: 2),\n"
"]\n");
ASSERT_TRUE(m.ok);
/* GREEN: RON has no import syntax; pipeline emits 0 IMPORTS edges. */
ASSERT_TRUE(m.imports == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 8 — Jsonnet (.jsonnet, .libsonnet)
*
* Labels histogram: Module:1
* Jsonnet is a data templating language. `import`/`importstr` for cross-file
* references — not yet resolved by the pipeline.
* ══════════════════════════════════════════════════════════════════ */
/* Jsonnet: object literal → Module node. */
TEST(probe_jsonnet_object_node) {
GpfMetrics m = gpf_metrics("config.jsonnet",
"{\n"
" local env = \"prod\",\n"
" host: \"api.example.com\",\n"
" port: 443,\n"
" debug: env == \"dev\",\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty Jsonnet file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Jsonnet: function definition in object. */
TEST(probe_jsonnet_function_in_object) {
GpfMetrics m = gpf_metrics("lib.jsonnet",
"{\n"
" double(x):: x * 2,\n"
" square(x):: x * x,\n"
" greet(name):: \"Hello, \" + name,\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Jsonnet: `import` in two-file fixture → IMPORTS edge.
* RED: Jsonnet import not resolved into IMPORTS edges. */
TEST(probe_jsonnet_import_edge) {
static const GpfFile files[] = {
{"utils.libsonnet",
"{\n"
" double(x):: x * 2,\n"
"}\n"},
{"main.jsonnet",
"local utils = import 'utils.libsonnet';\n"
"{\n"
" result: utils.double(21),\n"
"}\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Jsonnet `import` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Jsonnet import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 9 — KDL (.kdl)
*
* Labels histogram: Module:1
* KDL is a document language. No import mechanism; single Module per file.
* ══════════════════════════════════════════════════════════════════ */
/* KDL: node with children → Module node. */
TEST(probe_kdl_node_block) {
GpfMetrics m = gpf_metrics("config.kdl",
"server {\n"
" host \"localhost\"\n"
" port 8080\n"
"}\n"
"\n"
"database {\n"
" host \"db.internal\"\n"
" port 5432\n"
" name \"mydb\"\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: KDL file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* KDL: flat node list (key-value style). */
TEST(probe_kdl_flat_nodes) {
GpfMetrics m = gpf_metrics("manifest.kdl",
"package \"my-package\" version=\"1.0.0\"\n"
"description \"A test package\"\n"
"author \"Test Author\" email=\"test@example.com\"\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* KDL: no import mechanism → 0 IMPORTS edges. */
TEST(probe_kdl_no_imports) {
GpfMetrics m = gpf_metrics("data.kdl",
"items {\n"
" item id=1 name=\"alpha\"\n"
" item id=2 name=\"beta\"\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: KDL has no import syntax; 0 IMPORTS edges expected. */
ASSERT_TRUE(m.imports == 0);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 10 — Hyprlang (.conf, .hl, hyprland.conf)
*
* Labels histogram: Module:1
* Hyprlang is the Hyprland compositor config language. `source` directive
* for including other files — not resolved by the pipeline.
* ══════════════════════════════════════════════════════════════════ */
/* Hyprlang: section block → Module node. */
TEST(probe_hyprlang_section_node) {
GpfMetrics m = gpf_metrics("hyprland.conf",
"general {\n"
" gaps_in = 5\n"
" gaps_out = 10\n"
" border_size = 2\n"
" col.active_border = rgba(33ccffee)\n"
"}\n"
"\n"
"decoration {\n"
" rounding = 10\n"
" blur {\n"
" enabled = true\n"
" size = 3\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty Hyprlang file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Hyprlang: keybind and monitor directives. */
TEST(probe_hyprlang_directives) {
GpfMetrics m = gpf_metrics("binds.hl",
"bind = $mainMod, Q, killactive\n"
"bind = $mainMod, M, exit\n"
"bind = $mainMod, F, fullscreen\n"
"monitor = DP-1, 1920x1080@144, 0x0, 1\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node for any non-empty .hl file. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Hyprlang: `source` in two-file fixture → IMPORTS edge.
* RED: Hyprlang `source` directive not resolved into IMPORTS edges. */
TEST(probe_hyprlang_source_import_edge) {
static const GpfFile files[] = {
{"colors.conf",
"col.active_border = rgba(33ccffee)\n"
"col.inactive_border = rgba(595959aa)\n"},
{"hyprland.conf",
"source = ~/.config/hypr/colors.conf\n"
"\n"
"general {\n"
" gaps_in = 5\n"
"}\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Hyprlang `source` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Hyprlang source resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 11 — DeviceTree (.dts, .dtsi, .overlay)
*
* Labels histogram: Module:1
* DeviceTree Source describes hardware topology; node blocks like `/ { ... }`
* or named nodes like `cpu@0 { ... }`. `#include` is preprocessor-level;
* not modeled as IMPORTS in the pipeline.
* ══════════════════════════════════════════════════════════════════ */
/* DeviceTree: minimal DTS with root node → Module node. */
TEST(probe_devicetree_root_node) {
GpfMetrics m = gpf_metrics("board.dts",
"/dts-v1/;\n"
"\n"
"/ {\n"
" compatible = \"vendor,board\";\n"
" model = \"Vendor Board v1\";\n"
" #address-cells = <1>;\n"
" #size-cells = <1>;\n"
"};\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty DTS file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* DeviceTree: named child nodes (cpu, memory). */
TEST(probe_devicetree_child_nodes) {
GpfMetrics m = gpf_metrics("soc.dts",
"/dts-v1/;\n"
"\n"
"/ {\n"
" cpus {\n"
" #address-cells = <1>;\n"
" #size-cells = <0>;\n"
" cpu@0 {\n"
" compatible = \"arm,cortex-a53\";\n"
" reg = <0>;\n"
" };\n"
" cpu@1 {\n"
" compatible = \"arm,cortex-a53\";\n"
" reg = <1>;\n"
" };\n"
" };\n"
" memory@80000000 {\n"
" reg = <0x80000000 0x40000000>;\n"
" };\n"
"};\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node (child nodes not individually extracted in grammar-only path). */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* DeviceTree: .dtsi include fragment → IMPORTS edge.
* RED: DeviceTree `#include` is handled at preprocessor level, not by the pipeline
* as an IMPORTS graph edge. */
TEST(probe_devicetree_include_edge) {
static const GpfFile files[] = {
{"soc.dtsi",
"/dts-v1/;\n"
"/ {\n"
" soc: soc {\n"
" compatible = \"simple-bus\";\n"
" };\n"
"};\n"},
{"board.dts",
"/dts-v1/;\n"
"#include \"soc.dtsi\"\n"
"\n"
"/ {\n"
" model = \"My Board\";\n"
"};\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: DTS `#include` not resolved into IMPORTS edges (preprocessor-level). */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no DTS include resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 12 — CMake (CMakeLists.txt, .cmake)
*
* Labels histogram: Function:1, Module:1
* CMake's grammar extractor produces a Function node per defined function/macro
* plus a Module for the file. `include()` and `add_subdirectory()` are
* candidates for IMPORTS edges but not yet resolved.
* ══════════════════════════════════════════════════════════════════ */
/* CMake: function definition → Function node. */
TEST(probe_cmake_function_node) {
GpfMetrics m = gpf_metrics("CMakeLists.txt",
"cmake_minimum_required(VERSION 3.16)\n"
"project(MyProject)\n"
"\n"
"function(add_test_target name)\n"
" add_executable(${name} ${name}.cpp)\n"
" target_link_libraries(${name} PRIVATE gtest)\n"
"endfunction()\n");
ASSERT_TRUE(m.ok);
/* GREEN: CMake function() definition must produce a Function node. */
ASSERT_TRUE(m.functions >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* CMake: macro definition → Function node (macros map to Function label). */
TEST(probe_cmake_macro_node) {
GpfMetrics m = gpf_metrics("helpers.cmake",
"macro(print_var varname)\n"
" message(STATUS \"${varname} = ${${varname}}\")\n"
"endmacro()\n"
"\n"
"function(setup_target name)\n"
" add_library(${name} STATIC)\n"
"endfunction()\n");
ASSERT_TRUE(m.ok);
/* GREEN: both function() and macro() produce Function nodes. */
ASSERT_TRUE(m.functions >= 1);
PASS();
}
/* CMake: `include()` in two-file fixture → IMPORTS edge.
* RED: CMake include() not resolved into IMPORTS edges. */
TEST(probe_cmake_include_edge) {
static const GpfFile files[] = {
{"helpers.cmake",
"function(check_compiler)\n"
" message(STATUS \"Compiler: ${CMAKE_CXX_COMPILER_ID}\")\n"
"endfunction()\n"},
{"CMakeLists.txt",
"cmake_minimum_required(VERSION 3.16)\n"
"project(Foo)\n"
"include(helpers.cmake)\n"
"check_compiler()\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: CMake `include()` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no CMake include resolver */
PASS();
}
/* CMake: add_subdirectory → child CMakeLists.txt; IMPORTS edge expected.
* RED: add_subdirectory not resolved into IMPORTS edges. */
TEST(probe_cmake_subdirectory_edge) {
static const GpfFile files[] = {
{"CMakeLists.txt",
"cmake_minimum_required(VERSION 3.16)\n"
"project(Root)\n"
"add_subdirectory(lib)\n"},
{"lib/CMakeLists.txt",
"add_library(mylib STATIC mylib.cpp)\n"
"\n"
"function(mylib_configure target)\n"
" target_link_libraries(${target} PRIVATE mylib)\n"
"endfunction()\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: CMake add_subdirectory() not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no add_subdirectory resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 13 — Makefile (Makefile, .mk, GNUmakefile)
*
* Labels histogram: Function:1, Module:1
* The Makefile extractor produces a Function node for targets and a Module for
* the file. `include` directive is a candidate for IMPORTS but not resolved.
* ══════════════════════════════════════════════════════════════════ */
/* Makefile: phony targets → Function nodes. */
TEST(probe_makefile_target_nodes) {
GpfMetrics m = gpf_metrics("Makefile",
".PHONY: all build test clean\n"
"\n"
"all: build\n"
"\n"
"build:\n"
"\tgcc -o myapp main.c utils.c\n"
"\n"
"test:\n"
"\t./run_tests.sh\n"
"\n"
"clean:\n"
"\trm -f myapp *.o\n");
ASSERT_TRUE(m.ok);
/* GREEN: at least the first target must produce a Function node. */
ASSERT_TRUE(m.functions >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Makefile: pattern rules and variables. */
TEST(probe_makefile_pattern_rule) {
GpfMetrics m = gpf_metrics("rules.mk",
"CC = gcc\n"
"CFLAGS = -Wall -Wextra -O2\n"
"\n"
"%.o: %.c\n"
"\t$(CC) $(CFLAGS) -c $< -o $@\n"
"\n"
"install:\n"
"\tcp myapp /usr/local/bin/\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node for any non-empty Makefile. */
ASSERT_TRUE(m.total_nodes >= 1);
PASS();
}
/* Makefile: `include` in two-file fixture → IMPORTS edge.
* RED: Makefile `include` directive not resolved into IMPORTS edges. */
TEST(probe_makefile_include_edge) {
static const GpfFile files[] = {
{"common.mk",
"CC = gcc\n"
"CFLAGS = -Wall -O2\n"
"\n"
"clean:\n"
"\trm -f *.o\n"},
{"Makefile",
"include common.mk\n"
"\n"
"all:\n"
"\t$(CC) $(CFLAGS) -o myapp main.c\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Makefile `include` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Makefile include resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 14 — Meson (meson.build, meson.options)
*
* Labels histogram: Module:1
* Meson's grammar-only extractor produces a Module node per file. Individual
* build targets (executable(), library()) are not extracted as distinct nodes.
* `subdir()` maps to IMPORTS conceptually but is not resolved.
* ══════════════════════════════════════════════════════════════════ */
/* Meson: project + executable → Module node. */
TEST(probe_meson_project_node) {
GpfMetrics m = gpf_metrics("meson.build",
"project('myapp', 'c',\n"
" version: '1.0.0',\n"
" default_options: ['warning_level=2'])\n"
"\n"
"executable('myapp',\n"
" sources: ['main.c', 'utils.c'],\n"
" install: true)\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty meson.build produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Meson: options file → Module node. */
TEST(probe_meson_options_node) {
GpfMetrics m = gpf_metrics("meson.options",
"option('enable_tests', type: 'boolean', value: true,\n"
" description: 'Build and run tests')\n"
"option('build_type', type: 'combo', choices: ['debug', 'release'],\n"
" value: 'release')\n");
ASSERT_TRUE(m.ok);
/* GREEN: meson.options also produces a Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Meson: subdir() across two files → IMPORTS edge.
* RED: Meson subdir() not resolved into IMPORTS edges. */
TEST(probe_meson_subdir_edge) {
static const GpfFile files[] = {
{"meson.build",
"project('root', 'c')\n"
"subdir('lib')\n"},
{"lib/meson.build",
"static_library('mylib', 'mylib.c')\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Meson subdir() not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Meson subdir resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 15 — GN (.gn, .gni)
*
* Labels histogram: Module:1
* GN (Generate Ninja) build system for Chromium-like projects. The extractor
* produces a Module node per file. `import()` for shared .gni files is a
* candidate for IMPORTS but not yet resolved.
* ══════════════════════════════════════════════════════════════════ */
/* GN: executable target → Module node (individual targets not extracted). */
TEST(probe_gn_executable_node) {
GpfMetrics m = gpf_metrics("BUILD.gn",
"executable(\"myapp\") {\n"
" sources = [\n"
" \"main.cc\",\n"
" \"utils.cc\",\n"
" ]\n"
" deps = [ \"//third_party/base\" ]\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty .gn file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* GN: shared_library and static_library targets. */
TEST(probe_gn_library_targets) {
GpfMetrics m = gpf_metrics("lib.gn",
"static_library(\"crypto\") {\n"
" sources = [ \"crypto.cc\", \"sha256.cc\" ]\n"
"}\n"
"\n"
"shared_library(\"net\") {\n"
" sources = [ \"net.cc\", \"socket.cc\" ]\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* GN: import() of a .gni file → IMPORTS edge.
* RED: GN import() not resolved into IMPORTS edges. */
TEST(probe_gn_import_edge) {
static const GpfFile files[] = {
{"//build/common.gni",
"template(\"cc_binary\") {\n"
" executable(target_name) {\n"
" forward_variables_from(invoker, \"*\")\n"
" }\n"
"}\n"},
{"BUILD.gn",
"import(\"//build/common.gni\")\n"
"\n"
"cc_binary(\"myapp\") {\n"
" sources = [ \"main.cc\" ]\n"
"}\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: GN import() not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no GN import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 16 — Just (justfile, Justfile, .justfile)
*
* Labels histogram: Module:1
* Just is a command runner. Recipes are the build units; the grammar-only
* extractor currently produces only a Module node per file (recipes not
* individually extracted as Function nodes per the histogram).
* ══════════════════════════════════════════════════════════════════ */
/* Just: recipe definitions → at least Module node. */
TEST(probe_just_recipe_nodes) {
GpfMetrics m = gpf_metrics("justfile",
"build:\n"
" cargo build --release\n"
"\n"
"test:\n"
" cargo test\n"
"\n"
"clean:\n"
" cargo clean\n"
"\n"
"fmt:\n"
" cargo fmt\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty justfile produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Just: recipe nodes as Function nodes.
* RED: histogram shows Module:1 only — individual recipes are not extracted
* as Function nodes by the grammar-only Just extractor. */
TEST(probe_just_recipe_function_nodes) {
GpfMetrics m = gpf_metrics("Justfile",
"deploy env:\n"
" ./scripts/deploy.sh {{env}}\n"
"\n"
"lint:\n"
" golangci-lint run ./...\n"
"\n"
"generate:\n"
" go generate ./...\n");
ASSERT_TRUE(m.ok);
/* RED: Just recipes not extracted as Function nodes (only Module:1 in histogram). */
ASSERT_TRUE(m.functions >= 1); /* expected RED — no Just recipe → Function extraction */
PASS();
}
/* Just: `import` directive (Just >=1.5) → IMPORTS edge.
* RED: Just `import` not resolved into IMPORTS edges. */
TEST(probe_just_import_edge) {
static const GpfFile files[] = {
{"common.just",
"fmt:\n"
" cargo fmt\n"
"\n"
"lint:\n"
" cargo clippy\n"},
{"justfile",
"import 'common.just'\n"
"\n"
"build:\n"
" cargo build\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Just `import` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Just import resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 17 — BitBake (.bb, .bbappend, .bbclass)
*
* Labels histogram: Module:1
* BitBake is the build system used by Yocto/OpenEmbedded. Recipes define
* tasks (do_compile, do_install, etc.) and variables. The extractor produces
* a Module node per file; individual tasks not extracted as Function nodes.
* `inherit` and `require`/`include` are cross-file mechanisms.
* ══════════════════════════════════════════════════════════════════ */
/* BitBake: recipe with task and variable → Module node. */
TEST(probe_bitbake_recipe_node) {
GpfMetrics m = gpf_metrics("mypackage_1.0.bb",
"DESCRIPTION = \"A simple test package\"\n"
"HOMEPAGE = \"https://example.com\"\n"
"LICENSE = \"MIT\"\n"
"LIC_FILES_CHKSUM = \"file://COPYING;md5=abc123\"\n"
"\n"
"SRC_URI = \"https://example.com/mypackage-1.0.tar.gz\"\n"
"\n"
"do_compile() {\n"
" make\n"
"}\n"
"\n"
"do_install() {\n"
" install -d ${D}${bindir}\n"
" install -m 0755 myapp ${D}${bindir}/\n"
"}\n");
ASSERT_TRUE(m.ok);
/* GREEN: any non-empty .bb file produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* BitBake: bbclass file → Module node. */
TEST(probe_bitbake_bbclass_node) {
GpfMetrics m = gpf_metrics("cmake.bbclass",
"# CMake class for BitBake\n"
"EXTRA_OECMAKE ?= \"\"\n"
"\n"
"cmake_do_configure() {\n"
" cmake -DCMAKE_INSTALL_PREFIX=${prefix} ${EXTRA_OECMAKE} ${S}\n"
"}\n"
"\n"
"cmake_do_compile() {\n"
" make ${EXTRA_OEMAKE}\n"
"}\n"
"\n"
"EXPORT_FUNCTIONS do_configure do_compile\n");
ASSERT_TRUE(m.ok);
/* GREEN: .bbclass produces a Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* BitBake: `require` in two-file fixture → IMPORTS edge.
* RED: BitBake require/include not resolved into IMPORTS edges. */
TEST(probe_bitbake_require_edge) {
static const GpfFile files[] = {
{"mypackage.inc",
"DESCRIPTION = \"Shared package metadata\"\n"
"LICENSE = \"MIT\"\n"
"SRC_URI = \"https://example.com/pkg-${PV}.tar.gz\"\n"},
{"mypackage_1.0.bb",
"require mypackage.inc\n"
"\n"
"PV = \"1.0\"\n"
"\n"
"do_compile() {\n"
" make\n"
"}\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: BitBake `require` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no BitBake require resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 18 — Kconfig (Kconfig)
*
* Labels histogram: Class:1, Module:1
* Kconfig describes Linux kernel configuration options. The extractor
* produces a Module + a Class node (one per Kconfig file, matching histogram).
* `source` directive includes other Kconfig files — not resolved as IMPORTS.
* ══════════════════════════════════════════════════════════════════ */
/* Kconfig: config entries → Module and Class nodes. */
TEST(probe_kconfig_config_entries) {
GpfMetrics m = gpf_metrics("Kconfig",
"config MYDRV\n"
" tristate \"My Driver\"\n"
" depends on PCI\n"
" help\n"
" Enable this driver for My Device.\n"
"\n"
"config MYDRV_DEBUG\n"
" bool \"Enable debug output\"\n"
" depends on MYDRV\n"
" default n\n");
ASSERT_TRUE(m.ok);
/* GREEN: histogram confirms Class:1,Module:1 for any Kconfig file. */
ASSERT_TRUE(m.modules >= 1);
ASSERT_TRUE(m.classes >= 1);
PASS();
}
/* Kconfig: menu block with multiple entries. */
TEST(probe_kconfig_menu_block) {
GpfMetrics m = gpf_metrics("Kconfig",
"menu \"Network Support\"\n"
"\n"
"config NET\n"
" bool \"Networking support\"\n"
"\n"
"config INET\n"
" bool \"TCP/IP networking\"\n"
" depends on NET\n"
"\n"
"endmenu\n");
ASSERT_TRUE(m.ok);
/* GREEN: Module + Class nodes for the file. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Kconfig: `source` in two-file fixture → IMPORTS edge.
* RED: Kconfig `source` directive not resolved into IMPORTS edges. */
TEST(probe_kconfig_source_edge) {
static const GpfFile files[] = {
{"drivers/Kconfig",
"config DRV_USB\n"
" tristate \"USB Driver\"\n"
" depends on USB\n"},
{"Kconfig",
"mainmenu \"Linux Kernel Configuration\"\n"
"\n"
"source \"drivers/Kconfig\"\n"}
};
GpfMetrics m = gpf_metrics_files(files, 2);
ASSERT_TRUE(m.ok);
/* RED: Kconfig `source` not resolved into IMPORTS edges. */
ASSERT_TRUE(m.imports >= 1); /* expected RED — no Kconfig source resolver */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 19 — Go Mod (go.mod)
*
* Labels histogram: Module:1
* go.mod is parsed by pass_pkgmap.c which extracts the module path as a
* pkgmap entry used for import resolution, not for producing a DEPENDS_ON
* edge per `require` directive. A Module node is created per file.
* DEPENDS_ON edges are produced for Helm chart dependencies (pass_k8s.c),
* not for go.mod `require` blocks in the grammar-only pipeline.
* ══════════════════════════════════════════════════════════════════ */
/* Go mod: module directive → Module node. */
TEST(probe_gomod_module_node) {
GpfMetrics m = gpf_metrics("go.mod",
"module example.com/myapp\n"
"\n"
"go 1.21\n"
"\n"
"require (\n"
" github.com/gin-gonic/gin v1.9.1\n"
" github.com/stretchr/testify v1.8.4\n"
")\n");
ASSERT_TRUE(m.ok);
/* GREEN: go.mod produces at least a Module node. */
ASSERT_TRUE(m.total_nodes >= 1);
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Go mod: simple module with go directive only. */
TEST(probe_gomod_simple) {
GpfMetrics m = gpf_metrics("go.mod",
"module example.com/simple\n"
"\n"
"go 1.21\n");
ASSERT_TRUE(m.ok);
/* GREEN: even a minimal go.mod with no requires produces a Module node. */
ASSERT_TRUE(m.modules >= 1);
PASS();
}
/* Go mod: `require` blocks → DEPENDS_ON edges per dependency.
* RED: go.mod `require` directives are not resolved into DEPENDS_ON edges;
* pass_pkgmap only uses go.mod for intra-repo module path mapping. */
TEST(probe_gomod_depends_on_edges) {
GpfMetrics m = gpf_metrics("go.mod",
"module example.com/server\n"
"\n"
"go 1.21\n"
"\n"
"require (\n"
" github.com/gorilla/mux v1.8.1\n"
" github.com/sirupsen/logrus v1.9.3\n"
" github.com/prometheus/client_golang v1.17.0\n"
")\n");
ASSERT_TRUE(m.ok);
/* RED: go.mod require → DEPENDS_ON not implemented (pkgmap handles path mapping only). */
ASSERT_TRUE(m.depends_on >= 1); /* expected RED — no go.mod DEPENDS_ON producer */
PASS();
}
/* Go mod: go.mod used in a multi-file project to enable cross-package IMPORTS.
* GREEN: the presence of go.mod enables the pkgmap resolver to link
* cross-package imports in Go source files. */
TEST(probe_gomod_enables_go_imports) {
static const GpfFile files[] = {
{"go.mod",
"module example.com/demo\n"
"\n"
"go 1.21\n"},
{"pkg/calc/calc.go",
"package calc\n"
"\n"
"func Add(a, b int) int { return a + b }\n"},
{"main.go",
"package main\n"
"\n"
"import \"example.com/demo/pkg/calc\"\n"
"\n"
"func main() { _ = calc.Add(1, 2) }\n"}
};
GpfMetrics m = gpf_metrics_files(files, 3);
ASSERT_TRUE(m.ok);
/* GREEN: go.mod enables the pkgmap resolver; main.go → calc IMPORTS edge. */
ASSERT_TRUE(m.imports >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* SUITE wiring
* ══════════════════════════════════════════════════════════════════ */
SUITE(grammar_probe_f) {
/* HCL / Terraform */
RUN_TEST(probe_hcl_resource_nodes);
RUN_TEST(probe_hcl_class_node);
RUN_TEST(probe_hcl_module_block_node);
RUN_TEST(probe_hcl_infra_maps_edge);
RUN_TEST(probe_hcl_no_spurious_imports);
/* Kubernetes manifests */
RUN_TEST(probe_k8s_pod_resource_node);
RUN_TEST(probe_k8s_deployment_resource_node);
RUN_TEST(probe_k8s_two_manifests);
RUN_TEST(probe_k8s_infra_maps_edge);
/* Kustomize */
RUN_TEST(probe_kustomize_module_node);
RUN_TEST(probe_kustomize_imports_edges);
RUN_TEST(probe_kustomize_with_patches);
/* Nix */
RUN_TEST(probe_nix_attrset_node);
RUN_TEST(probe_nix_let_binding);
RUN_TEST(probe_nix_import_edge);
/* Nickel */
RUN_TEST(probe_nickel_record_node);
RUN_TEST(probe_nickel_let_fn);
RUN_TEST(probe_nickel_import_edge);
/* Pkl */
RUN_TEST(probe_pkl_properties_node);
RUN_TEST(probe_pkl_object_block);
RUN_TEST(probe_pkl_import_edge);
/* RON */
RUN_TEST(probe_ron_record_node);
RUN_TEST(probe_ron_named_struct);
RUN_TEST(probe_ron_no_imports);
/* Jsonnet */
RUN_TEST(probe_jsonnet_object_node);
RUN_TEST(probe_jsonnet_function_in_object);
RUN_TEST(probe_jsonnet_import_edge);
/* KDL */
RUN_TEST(probe_kdl_node_block);
RUN_TEST(probe_kdl_flat_nodes);
RUN_TEST(probe_kdl_no_imports);
/* Hyprlang */
RUN_TEST(probe_hyprlang_section_node);
RUN_TEST(probe_hyprlang_directives);
RUN_TEST(probe_hyprlang_source_import_edge);
/* DeviceTree */
RUN_TEST(probe_devicetree_root_node);
RUN_TEST(probe_devicetree_child_nodes);
RUN_TEST(probe_devicetree_include_edge);
/* CMake */
RUN_TEST(probe_cmake_function_node);
RUN_TEST(probe_cmake_macro_node);
RUN_TEST(probe_cmake_include_edge);
RUN_TEST(probe_cmake_subdirectory_edge);
/* Makefile */
RUN_TEST(probe_makefile_target_nodes);
RUN_TEST(probe_makefile_pattern_rule);
RUN_TEST(probe_makefile_include_edge);
/* Meson */
RUN_TEST(probe_meson_project_node);
RUN_TEST(probe_meson_options_node);
RUN_TEST(probe_meson_subdir_edge);
/* GN */
RUN_TEST(probe_gn_executable_node);
RUN_TEST(probe_gn_library_targets);
RUN_TEST(probe_gn_import_edge);
/* Just */
RUN_TEST(probe_just_recipe_nodes);
RUN_TEST(probe_just_recipe_function_nodes);
RUN_TEST(probe_just_import_edge);
/* BitBake */
RUN_TEST(probe_bitbake_recipe_node);
RUN_TEST(probe_bitbake_bbclass_node);
RUN_TEST(probe_bitbake_require_edge);
/* Kconfig */
RUN_TEST(probe_kconfig_config_entries);
RUN_TEST(probe_kconfig_menu_block);
RUN_TEST(probe_kconfig_source_edge);
/* Go Mod */
RUN_TEST(probe_gomod_module_node);
RUN_TEST(probe_gomod_simple);
RUN_TEST(probe_gomod_depends_on_edges);
RUN_TEST(probe_gomod_enables_go_imports);
}