Files
codebase-memory-mcp/tests/test_node_creation_probe.c
T
Andrew Hundt 0370d52f4d fix: harden index persistence and MCP outputs
Make index writes and artifact refreshes use verified temp files plus atomic replacement, preserving Windows replace semantics and avoiding fixed temp-path collisions.

Thread the remaining pipeline confidence knobs through config while keeping zero-valued defaults backward compatible with existing upstream behavior.

Tighten route-node gates and architecture route output so infra URL nodes stay queryable in the graph but do not pollute get_architecture route summaries; preserve code-created absolute URL routes for all language extractors.

Harden MCP and CLI path handling around cache paths, grep scratch files, update downloads, checksum validation, stale checks, and project DB discovery without writing protocol noise to stdout.

Add regression coverage for corrupt-DB recovery, concurrent atomic writes, tmp-prefixed project DBs, duplicate search result fields, route output filtering, config registry entries, and focused suite selectors.

Validated with: git diff --check; make -f Makefile.cbm build/c/test-runner; focused suites security, artifact, graph_buffer, sqlite_writer, store_search, store_bulk, store_pragmas, store_checkpoint, watcher, infrascan, pipeline, store_nodes, mcp, cli, parallel, tool_consolidation, incremental, and store_arch; make -f Makefile.cbm cbm; isolated dogfood index/query run.

Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-06-28 06:34:23 -04:00

1798 lines
83 KiB
C

/*
* test_node_creation_probe.c — Reproduce-first node-creation probe.
*
* Green = guard (node/edge already created correctly).
* Red = bug found (node/edge not produced; keep until fixed).
*
* Each per-language group indexes a fixture through the FULL pipeline via the
* same lang_index / lang_metrics harness as test_lang_contract.c (the helpers
* are copy-included here so this file compiles stand-alone without touching
* test_main.c or test_lang_contract.c).
*
* Coverage strategy:
* - 9 hybrid-LSP languages (Go, C, C++, Rust, Python, TypeScript, Java,
* Kotlin, C#): functions, methods, classes/structs, enums, interfaces/
* traits, nested functions, lambdas/closures, generics, type aliases,
* constants/global vars. ~6-12 cases each.
* - 14 grammar-only languages (Ruby, Swift, Scala, Lua, Bash, Dart, Elixir,
* Haskell, Zig, OCaml, Erlang, Groovy, Nim, GDScript): functions + a
* class/module + intra-file call. ~3-6 cases each.
*
* How languages were confirmed supported: each uses a CBM_LANG_* constant
* from cbm.h (the canonical enum); the label-golden table in
* test_grammar_labels.c provides the expected histogram per grammar.
*
* Known expected-RED cases (document the gap, keep for fix-phase):
* - Dart CALLS edge: extract_calls.c has no dart branch (selector call).
* - Groovy CALLS edge: function_call callee resolution unhandled.
*
* Do NOT register in test_main.c — run via standalone suite.
*/
#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 — copy of the static helpers from test_lang_contract.c.
* (Duplicated here so the probe file is self-contained and does not
* require test_main.c changes.)
* ══════════════════════════════════════════════════════════════════ */
typedef struct {
char tmpdir[256];
char dbpath[512];
char *project;
cbm_mcp_server_t *srv;
} NcpLangProj;
typedef struct {
const char *name;
const char *content;
} NcpLangFile;
static void ncp_to_fwd_slashes(char *p) {
for (; *p; p++) {
if (*p == '\\')
*p = '/';
}
}
static cbm_store_t *ncp_open_indexed(NcpLangProj *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];
/* Honor CBM_CACHE_DIR so this matches the pipeline write path (test isolation). */
snprintf(cache_dir, sizeof(cache_dir), "%s",
cbm_resolve_cache_dir() ? cbm_resolve_cache_dir() : "/tmp");
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 *ncp_index_files(NcpLangProj *lp, const NcpLangFile *files, int nfiles) {
memset(lp, 0, sizeof(*lp));
snprintf(lp->tmpdir, sizeof(lp->tmpdir), "/tmp/cbm_ncp_XXXXXX");
if (!cbm_mkdtemp(lp->tmpdir))
return NULL;
ncp_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 ncp_open_indexed(lp);
}
static void ncp_cleanup(NcpLangProj *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);
}
/* Count nodes with a given label. Returns -1 on error. */
static int ncp_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;
}
/* Sum type-like nodes (Class + Struct + Interface + Enum + Trait + Type). */
static int ncp_type_nodes(cbm_store_t *store, const char *project) {
static const char *labels[] = {"Class", "Struct", "Interface", "Enum", "Trait", "Type", NULL};
int total = 0;
for (int i = 0; labels[i]; i++) {
int n = ncp_count_label(store, project, labels[i]);
if (n > 0)
total += n;
}
return total;
}
/* Callable nodes (Function + Method) with >=1 outbound edge. */
static int ncp_callables_with_outbound(cbm_store_t *store, const char *project) {
static const char *callable_labels[] = {"Function", "Method", NULL};
int total = 0;
for (int i = 0; callable_labels[i]; i++) {
cbm_search_params_t p = {0};
p.project = project;
p.label = callable_labels[i];
p.min_degree = 1;
p.max_degree = -1;
p.limit = 100;
cbm_search_output_t out = {0};
if (cbm_store_search(store, &p, &out) == CBM_STORE_OK)
total += out.count;
cbm_store_search_free(&out);
}
return total;
}
/* Metrics collected in one index pass. */
typedef struct {
int ok;
int total_nodes;
int functions;
int methods;
int classes; /* Class */
int structs; /* Struct */
int enums; /* Enum */
int interfaces; /* Interface */
int traits; /* Trait */
int types; /* all type-like combined */
int calls; /* CALLS edges */
int callers; /* callables with outbound */
int imports; /* IMPORTS edges */
} NcpMetrics;
static NcpMetrics ncp_metrics_files(const NcpLangFile *files, int nfiles) {
NcpLangProj lp;
cbm_store_t *store = ncp_index_files(&lp, files, nfiles);
NcpMetrics m = {0};
if (store) {
m.ok = 1;
m.total_nodes = cbm_store_count_nodes(store, lp.project);
m.functions = ncp_count_label(store, lp.project, "Function");
m.methods = ncp_count_label(store, lp.project, "Method");
m.classes = ncp_count_label(store, lp.project, "Class");
m.structs = ncp_count_label(store, lp.project, "Struct");
m.enums = ncp_count_label(store, lp.project, "Enum");
m.interfaces = ncp_count_label(store, lp.project, "Interface");
m.traits = ncp_count_label(store, lp.project, "Trait");
m.types = ncp_type_nodes(store, lp.project);
m.calls = cbm_store_count_edges_by_type(store, lp.project, "CALLS");
m.callers = ncp_callables_with_outbound(store, lp.project);
m.imports = cbm_store_count_edges_by_type(store, lp.project, "IMPORTS");
}
ncp_cleanup(&lp, store);
return m;
}
static NcpMetrics ncp_metrics(const char *filename, const char *content) {
NcpLangFile f = {filename, content};
return ncp_metrics_files(&f, 1);
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 1 — Go (hybrid LSP)
* Labels: Function, Module
* ══════════════════════════════════════════════════════════════════ */
/* Go: two top-level functions; both must become Function nodes. */
TEST(probe_go_functions) {
NcpMetrics m = ncp_metrics("svc.go", "package svc\n\n"
"func add(a, b int) int { return a + b }\n\n"
"func sub(a, b int) int { return a - b }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* Go: intra-package CALLS edge (function -> function). */
TEST(probe_go_calls_edge) {
NcpMetrics m =
ncp_metrics("calc.go", "package calc\n\n"
"func double(n int) int { return n * 2 }\n\n"
"func quadruple(n int) int { return double(double(n)) }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.calls >= 1);
ASSERT_TRUE(m.callers >= 1);
PASS();
}
/* Go: variadic + named return (syntactic variants must not drop the node). */
TEST(probe_go_variadic_named_return) {
NcpMetrics m = ncp_metrics("util.go", "package util\n\n"
"func sum(vals ...int) (total int) {\n"
" for _, v := range vals { total += v }\n"
" return\n"
"}\n\n"
"func wrap(vals ...int) int { return sum(vals...) }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Go: method on a named struct type.
* ASSERTION CORRECTED: the original probe assumed "Go labels methods as Function",
* but extract_defs.c labels Go RECEIVER methods as "Method" (def.receiver set,
* label="Method"); only the plain top-level NewCounter is a "Function". So the
* correct expectation is 3 callables total (NewCounter Function + Inc/Get Methods),
* i.e. functions + methods >= 3, not functions >= 3. */
TEST(probe_go_method_on_struct) {
NcpMetrics m = ncp_metrics("model.go", "package model\n\n"
"type Counter struct{ n int }\n\n"
"func (c *Counter) Inc() { c.n++ }\n\n"
"func (c *Counter) Get() int { return c.n }\n\n"
"func NewCounter() *Counter { return &Counter{} }\n");
ASSERT_TRUE(m.ok);
/* NewCounter is a Function; Inc + Get are Methods (receiver methods). */
ASSERT_TRUE(m.functions + m.methods >= 3);
PASS();
}
/* Go: interface declaration must produce a type-like node. */
TEST(probe_go_interface_node) {
NcpMetrics m =
ncp_metrics("iface.go", "package iface\n\n"
"type Reader interface{ Read(p []byte) (int, error) }\n\n"
"type Writer interface{ Write(p []byte) (int, error) }\n\n"
"func Noop() {}\n");
ASSERT_TRUE(m.ok);
/* Go interfaces are typically labelled Class or Interface. */
ASSERT_TRUE(m.types >= 1 || m.functions >= 1);
PASS();
}
/* Go: closure assigned to a variable must not prevent enclosing function from
* appearing as a node. */
TEST(probe_go_closure) {
NcpMetrics m = ncp_metrics("cb.go", "package cb\n\n"
"func MakeAdder(base int) func(int) int {\n"
" return func(x int) int { return base + x }\n"
"}\n\n"
"func Apply(f func(int) int, n int) int { return f(n) }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* Go: generic function (Go 1.18+) must still create a Function node. */
TEST(probe_go_generic_function) {
NcpMetrics m =
ncp_metrics("gen.go", "package gen\n\n"
"func Map[T, U any](s []T, f func(T) U) []U {\n"
" r := make([]U, len(s))\n"
" for i, v := range s { r[i] = f(v) }\n"
" return r\n"
"}\n\n"
"func Filter[T any](s []T, pred func(T) bool) []T {\n"
" var r []T\n"
" for _, v := range s { if pred(v) { r = append(r, v) } }\n"
" return r\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 2 — C (hybrid LSP)
* Labels: Function, Module
* ══════════════════════════════════════════════════════════════════ */
/* C: two top-level functions → 2 Function nodes. */
TEST(probe_c_functions) {
NcpMetrics m = ncp_metrics("math.c", "static int add(int a, int b) { return a + b; }\n"
"int run(int x) { return add(x, 1); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* C: intra-file CALLS edge attributed to a Function, not the Module. */
TEST(probe_c_calls_edge) {
NcpMetrics m = ncp_metrics("proc.c", "static void helper(int x) { (void)x; }\n"
"void run(void) { helper(42); helper(1); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.calls >= 1);
ASSERT_TRUE(m.callers >= 1);
PASS();
}
/* C: function pointer typedef must not prevent the wrapping function node. */
TEST(probe_c_function_pointer) {
NcpMetrics m = ncp_metrics("fp.c", "typedef int (*transform_fn)(int);\n"
"static int double_it(int x) { return x * 2; }\n"
"int apply(transform_fn fn, int v) { return fn(v); }\n"
"int main(void) { return apply(double_it, 21); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 3); /* double_it, apply, main */
PASS();
}
/* C: macro + inline function — inline keyword must not hide the function. */
TEST(probe_c_inline_function) {
NcpMetrics m = ncp_metrics("inl.c", "static inline int clamp(int v, int lo, int hi) {\n"
" return v < lo ? lo : v > hi ? hi : v;\n"
"}\n"
"int normalise(int v) { return clamp(v, 0, 100); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* C: struct declaration creates at least a type-like node (or is labelled
* Class/Struct/Type depending on the extractor). */
TEST(probe_c_struct_node) {
NcpMetrics m = ncp_metrics("point.c", "typedef struct { float x; float y; } Point;\n"
"float dist_sq(Point a, Point b) {\n"
" float dx = a.x - b.x, dy = a.y - b.y;\n"
" return dx*dx + dy*dy;\n"
"}\n");
ASSERT_TRUE(m.ok);
/* The function must always appear. */
ASSERT_TRUE(m.functions >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 3 — C++ (hybrid LSP)
* Labels: Class:1, Function:1, Module:1 (from grammar golden)
* ══════════════════════════════════════════════════════════════════ */
/* C++: class with constructor + methods → Class node + Method/Function nodes. */
TEST(probe_cpp_class_methods) {
NcpMetrics m = ncp_metrics("counter.cpp", "#include <cstdio>\n"
"class Counter {\n"
" int n;\n"
"public:\n"
" Counter() : n(0) {}\n"
" void inc() { ++n; }\n"
" int get() const { return n; }\n"
"};\n"
"int main() {\n"
" Counter c;\n"
" c.inc(); c.inc();\n"
" return c.get();\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* Counter class */
ASSERT_TRUE(m.functions + m.methods >= 1);
PASS();
}
/* C++: class -> class CALLS edge. */
TEST(probe_cpp_calls_edge) {
NcpMetrics m =
ncp_metrics("calc.cpp", "static int square(int x) { return x * x; }\n"
"int sumSquares(int a, int b) { return square(a) + square(b); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.calls >= 1);
ASSERT_TRUE(m.callers >= 1);
PASS();
}
/* C++: template function — template keyword must not prevent the node. */
TEST(probe_cpp_template_function) {
NcpMetrics m = ncp_metrics("tpl.cpp", "template <typename T>\n"
"T clamp(T v, T lo, T hi) {\n"
" return v < lo ? lo : v > hi ? hi : v;\n"
"}\n"
"int apply() { return clamp<int>(5, 0, 10); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1);
PASS();
}
/* C++: enum class must produce a type node. */
TEST(probe_cpp_enum_class) {
NcpMetrics m = ncp_metrics("color.cpp", "enum class Color { Red, Green, Blue };\n"
"bool isRed(Color c) { return c == Color::Red; }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1);
/* Enum may or may not be modeled as a node; at least function must appear. */
PASS();
}
/* C++: struct with operator overload. */
TEST(probe_cpp_struct_operator) {
NcpMetrics m =
ncp_metrics("vec.cpp", "struct Vec2 { float x, y; };\n"
"Vec2 operator+(Vec2 a, Vec2 b) { return {a.x+b.x, a.y+b.y}; }\n"
"float dot(Vec2 a, Vec2 b) { return a.x*b.x + a.y*b.y; }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1 || m.functions >= 1);
PASS();
}
/* C++: inheritance — derived class must appear as a type node. */
TEST(probe_cpp_inheritance) {
NcpMetrics m =
ncp_metrics("shape.cpp", "class Shape {\n"
"public:\n"
" virtual double area() const = 0;\n"
"};\n"
"class Circle : public Shape {\n"
" double r;\n"
"public:\n"
" Circle(double r) : r(r) {}\n"
" double area() const override { return 3.14159 * r * r; }\n"
"};\n"
"double total(Circle c) { return c.area(); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 4 — Rust (hybrid LSP)
* Labels: Class:1, Function:1, Module:1 (struct -> Class)
* ══════════════════════════════════════════════════════════════════ */
/* Rust: struct + impl with multiple methods. */
TEST(probe_rust_struct_impl) {
NcpMetrics m =
ncp_metrics("stack.rs", "struct Stack<T> { items: Vec<T> }\n\n"
"impl<T> Stack<T> {\n"
" fn new() -> Self { Stack { items: Vec::new() } }\n"
" fn push(&mut self, item: T) { self.items.push(item); }\n"
" fn pop(&mut self) -> Option<T> { self.items.pop() }\n"
" fn is_empty(&self) -> bool { self.items.is_empty() }\n"
"}\n"
"fn demo() {\n"
" let mut s: Stack<i32> = Stack::new();\n"
" s.push(1); let _ = s.pop();\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* struct Stack */
ASSERT_TRUE(m.functions + m.methods >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Rust: enum with variants. */
TEST(probe_rust_enum) {
NcpMetrics m = ncp_metrics("result.rs",
"#[derive(Debug)]\n"
"enum MyResult { Ok(i32), Err(String) }\n\n"
"fn wrap(v: i32) -> MyResult { MyResult::Ok(v) }\n\n"
"fn unwrap_or(r: MyResult, default: i32) -> i32 {\n"
" match r { MyResult::Ok(v) => v, MyResult::Err(_) => default }\n"
"}\n");
ASSERT_TRUE(m.ok);
/* enum and functions must appear */
ASSERT_TRUE(m.types >= 1 || m.functions >= 1);
PASS();
}
/* Rust: trait definition + impl for a struct. */
TEST(probe_rust_trait_impl) {
NcpMetrics m =
ncp_metrics("greet.rs", "trait Greet {\n"
" fn hello(&self) -> String;\n"
"}\n\n"
"struct English;\n"
"struct Spanish;\n\n"
"impl Greet for English {\n"
" fn hello(&self) -> String { String::from(\"hello\") }\n"
"}\n"
"impl Greet for Spanish {\n"
" fn hello(&self) -> String { String::from(\"hola\") }\n"
"}\n"
"fn greet_all(gs: &[&dyn Greet]) {\n"
" for g in gs { println!(\"{}\", g.hello()); }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* trait and/or struct nodes */
ASSERT_TRUE(m.functions + m.methods >= 1);
PASS();
}
/* Rust: intra-file CALLS edge. */
TEST(probe_rust_calls_edge) {
NcpMetrics m =
ncp_metrics("math.rs", "fn square(x: i32) -> i32 { x * x }\n\n"
"fn sum_squares(a: i32, b: i32) -> i32 { square(a) + square(b) }\n\n"
"fn main() { let _ = sum_squares(3, 4); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.calls >= 1);
ASSERT_TRUE(m.callers >= 1);
PASS();
}
/* Rust: closure must not hide the enclosing function node. */
TEST(probe_rust_closure) {
NcpMetrics m = ncp_metrics("fmap.rs", "fn double_all(v: &[i32]) -> Vec<i32> {\n"
" v.iter().map(|x| x * 2).collect()\n"
"}\n\n"
"fn sum(v: &[i32]) -> i32 { v.iter().sum() }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* Rust: type alias must not prevent the surrounding function nodes. */
TEST(probe_rust_type_alias) {
NcpMetrics m =
ncp_metrics("alias.rs", "type Meters = f64;\n"
"type Seconds = f64;\n\n"
"fn speed(dist: Meters, time: Seconds) -> f64 { dist / time }\n\n"
"fn run() -> f64 { speed(100.0, 9.58) }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 5 — Python (hybrid LSP)
* Labels: Class:1, Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Python: top-level functions → Function nodes. */
TEST(probe_python_functions) {
NcpMetrics m = ncp_metrics("math.py", "def add(a, b):\n return a + b\n\n\n"
"def mul(a, b):\n return a * b\n\n\n"
"def run():\n return add(mul(2, 3), 1)\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 3);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Python: class with methods → Class node + Function/Method nodes. */
TEST(probe_python_class) {
NcpMetrics m = ncp_metrics("model.py", "class Account:\n"
" def __init__(self, owner, balance=0):\n"
" self.owner = owner\n"
" self.balance = balance\n\n"
" def deposit(self, amount):\n"
" self.balance += amount\n\n"
" def withdraw(self, amount):\n"
" if amount <= self.balance:\n"
" self.balance -= amount\n"
" return True\n"
" return False\n\n"
" def get_balance(self):\n"
" return self.balance\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* class Account */
ASSERT_TRUE(m.functions + m.methods >= 3);
PASS();
}
/* Python: nested function must not prevent outer function node. */
TEST(probe_python_nested_function) {
NcpMetrics m = ncp_metrics("closure.py", "def make_counter(start=0):\n"
" count = [start]\n\n"
" def inc():\n"
" count[0] += 1\n"
" return count[0]\n\n"
" return inc\n\n\n"
"def demo():\n"
" c = make_counter()\n"
" return c()\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2); /* make_counter, demo (inc is nested) */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Python: decorator syntax must not prevent the decorated function node. */
TEST(probe_python_decorator) {
NcpMetrics m = ncp_metrics("deco.py", "def log_call(fn):\n"
" def wrapper(*args, **kwargs):\n"
" result = fn(*args, **kwargs)\n"
" return result\n"
" return wrapper\n\n\n"
"@log_call\n"
"def compute(x):\n"
" return x * x\n\n\n"
"@log_call\n"
"def add(a, b):\n"
" return a + b\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 3); /* log_call, compute, add */
PASS();
}
/* Python: lambda in assignment must not prevent surrounding function. */
TEST(probe_python_lambda) {
NcpMetrics m = ncp_metrics("lmb.py", "double = lambda x: x * 2\n\n\n"
"def apply(fn, items):\n"
" return [fn(i) for i in items]\n\n\n"
"def run():\n"
" return apply(double, [1, 2, 3])\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2); /* apply + run */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Python: dataclass + multiple methods. */
TEST(probe_python_dataclass) {
NcpMetrics m =
ncp_metrics("point.py", "from dataclasses import dataclass\n\n\n"
"@dataclass\n"
"class Point:\n"
" x: float\n"
" y: float\n\n"
" def distance_sq(self, other):\n"
" return (self.x - other.x)**2 + (self.y - other.y)**2\n\n"
" def move(self, dx, dy):\n"
" return Point(self.x + dx, self.y + dy)\n\n\n"
"def origin():\n"
" return Point(0.0, 0.0)\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.functions >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 6 — TypeScript (hybrid LSP)
* Labels: Class:1, Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* TypeScript: interface + class implementing it. */
TEST(probe_ts_interface_class) {
NcpMetrics m =
ncp_metrics("iface.ts", "interface Shape {\n"
" area(): number;\n"
" perimeter(): number;\n"
"}\n\n"
"class Rectangle implements Shape {\n"
" constructor(private w: number, private h: number) {}\n"
" area(): number { return this.w * this.h; }\n"
" perimeter(): number { return 2 * (this.w + this.h); }\n"
"}\n\n"
"function describe(s: Shape): string {\n"
" return `area=${s.area()} perimeter=${s.perimeter()}`;\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* class Rectangle, possibly interface Shape */
ASSERT_TRUE(m.functions + m.methods >= 1);
PASS();
}
/* TypeScript: generic function + arrow function. */
TEST(probe_ts_generic_arrow) {
NcpMetrics m =
ncp_metrics("util.ts", "export function identity<T>(x: T): T { return x; }\n\n"
"export const double = (n: number): number => n * 2;\n\n"
"export function applyTwice<T>(f: (x: T) => T, x: T): T {\n"
" return f(f(x));\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1); /* identity + applyTwice at minimum */
PASS();
}
/* TypeScript: enum must produce a type-like node. */
TEST(probe_ts_enum) {
NcpMetrics m = ncp_metrics(
"status.ts",
"export enum Status { Active = 'active', Inactive = 'inactive', Pending = 'pending' }\n\n"
"export function isActive(s: Status): boolean {\n"
" return s === Status.Active;\n"
"}\n\n"
"export function toggle(s: Status): Status {\n"
" return s === Status.Active ? Status.Inactive : Status.Active;\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* TypeScript: CALLS edge via intra-file call. */
TEST(probe_ts_calls_edge) {
NcpMetrics m = ncp_metrics("chain.ts",
"function square(n: number): number { return n * n; }\n\n"
"function cube(n: number): number { return n * square(n); }\n\n"
"function run(n: number): number { return cube(n) + square(n); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.calls >= 1);
ASSERT_TRUE(m.callers >= 1);
PASS();
}
/* TypeScript: class inheritance. */
TEST(probe_ts_class_extends) {
NcpMetrics m = ncp_metrics(
"vehicle.ts", "class Vehicle {\n"
" constructor(public speed: number) {}\n"
" move(): string { return `moving at ${this.speed}`; }\n"
"}\n\n"
"class Car extends Vehicle {\n"
" constructor(speed: number, public brand: string) { super(speed); }\n"
" describe(): string { return `${this.brand}: ${this.move()}`; }\n"
"}\n\n"
"function demo(): string {\n"
" const c = new Car(100, 'Toyota');\n"
" return c.describe();\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
PASS();
}
/* TypeScript: type alias + union type function. */
TEST(probe_ts_type_alias) {
NcpMetrics m = ncp_metrics(
"types.ts", "type ID = string | number;\n"
"type Result<T> = { ok: true; value: T } | { ok: false; error: string };\n\n"
"function parseId(raw: string): Result<ID> {\n"
" const n = Number(raw);\n"
" return isNaN(n) ? { ok: true, value: raw } : { ok: true, value: n };\n"
"}\n\n"
"function formatId(id: ID): string { return String(id); }\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 7 — Java (hybrid LSP)
* Labels: Class:1, Method:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Java: class + multiple methods → Class node + Method nodes. */
TEST(probe_java_class_methods) {
NcpMetrics m =
ncp_metrics("Calc.java", "package app;\n\n"
"class Calc {\n"
" private int base;\n\n"
" public Calc(int base) { this.base = base; }\n\n"
" public int add(int x) { return base + x; }\n\n"
" public int mul(int x) { return base * x; }\n\n"
" public int addThenMul(int x) { return mul(add(x)); }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* class Calc */
ASSERT_TRUE(m.methods >= 2); /* add, mul, addThenMul */
ASSERT_TRUE(m.calls >= 1); /* addThenMul -> add + mul */
PASS();
}
/* Java: interface declaration → type-like node. */
TEST(probe_java_interface) {
NcpMetrics m = ncp_metrics("Printable.java",
"package app;\n\n"
"interface Printable {\n"
" String format();\n"
" default void print() { System.out.println(format()); }\n"
"}\n\n"
"class Doc implements Printable {\n"
" private String text;\n"
" Doc(String t) { this.text = t; }\n"
" public String format() { return \"[\" + text + \"]\"; }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
PASS();
}
/* Java: enum → type-like node. */
TEST(probe_java_enum) {
NcpMetrics m = ncp_metrics(
"Day.java",
"package app;\n\n"
"enum Day { MON, TUE, WED, THU, FRI, SAT, SUN;\n"
" public boolean isWeekend() {\n"
" return this == SAT || this == SUN;\n"
" }\n"
"}\n\n"
"class DayUtil {\n"
" static String label(Day d) { return d.isWeekend() ? \"rest\" : \"work\"; }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
PASS();
}
/* Java: inner class inside outer class. */
TEST(probe_java_inner_class) {
NcpMetrics m = ncp_metrics("Outer.java", "package app;\n\n"
"class Outer {\n"
" private int x;\n"
" Outer(int x) { this.x = x; }\n\n"
" class Inner {\n"
" int doubled() { return x * 2; }\n"
" }\n\n"
" int run() {\n"
" return new Inner().doubled();\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* Outer (and optionally Inner) */
PASS();
}
/* Java: lambda / method reference — enclosing method node must still appear. */
TEST(probe_java_lambda) {
NcpMetrics m = ncp_metrics(
"Streams.java",
"package app;\n\n"
"import java.util.List;\n"
"import java.util.stream.Collectors;\n\n"
"class Streams {\n"
" static List<Integer> doubleAll(List<Integer> xs) {\n"
" return xs.stream().map(x -> x * 2).collect(Collectors.toList());\n"
" }\n"
" static List<String> names(List<Object> objs) {\n"
" return objs.stream().map(Object::toString).collect(Collectors.toList());\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.methods >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 8 — Kotlin (hybrid LSP)
* Labels: Class:1, Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Kotlin: data class + companion object + extension function. */
TEST(probe_kotlin_data_class) {
NcpMetrics m = ncp_metrics("User.kt", "data class User(val name: String, val age: Int) {\n"
" fun greet(): String = \"Hi, I'm ${name}\"\n\n"
" companion object {\n"
" fun anonymous(): User = User(\"anon\", 0)\n"
" }\n"
"}\n\n"
"fun User.isAdult(): Boolean = age >= 18\n\n"
"fun demo(): String {\n"
" val u = User.anonymous()\n"
" return u.greet()\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* data class User */
ASSERT_TRUE(m.functions + m.methods >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Kotlin: sealed class hierarchy → type nodes. */
TEST(probe_kotlin_sealed_class) {
NcpMetrics m =
ncp_metrics("Result.kt", "sealed class Result<out T> {\n"
" data class Success<T>(val value: T) : Result<T>()\n"
" data class Failure(val error: String) : Result<Nothing>()\n"
"}\n\n"
"fun <T> unwrap(r: Result<T>, default: T): T =\n"
" when (r) {\n"
" is Result.Success -> r.value\n"
" is Result.Failure -> default\n"
" }\n\n"
"fun run(): String = unwrap(Result.Success(\"ok\"), \"fail\")\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
PASS();
}
/* Kotlin: interface + lambda / higher-order function. */
TEST(probe_kotlin_interface_lambda) {
NcpMetrics m = ncp_metrics("Filter.kt",
"interface Predicate<T> {\n"
" fun test(item: T): Boolean\n"
"}\n\n"
"fun <T> filterList(items: List<T>, pred: Predicate<T>): List<T> =\n"
" items.filter { pred.test(it) }\n\n"
"fun evens(xs: List<Int>): List<Int> =\n"
" filterList(xs, object : Predicate<Int> { override fun "
"test(item: Int) = item % 2 == 0 })\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.functions >= 1);
PASS();
}
/* Kotlin: CALLS edge between top-level functions. */
TEST(probe_kotlin_calls_edge) {
NcpMetrics m =
ncp_metrics("Math.kt", "fun square(n: Int): Int = n * n\n\n"
"fun cube(n: Int): Int = n * square(n)\n\n"
"fun sumOfCubes(a: Int, b: Int): Int = cube(a) + cube(b)\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.calls >= 1);
ASSERT_TRUE(m.callers >= 1);
PASS();
}
/* Kotlin: enum class. */
TEST(probe_kotlin_enum) {
NcpMetrics m = ncp_metrics(
"Color.kt", "enum class Color(val hex: String) {\n"
" RED(\"#FF0000\"), GREEN(\"#00FF00\"), BLUE(\"#0000FF\");\n\n"
" fun isDark(): Boolean = hex.substring(1).toLong(16) < 0x888888L\n"
"}\n\n"
"fun redHex(): String = Color.RED.hex\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* enum class Color */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 9 — C# (hybrid LSP)
* Labels: Class:1, Method:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* C#: class + multiple methods + intra-class CALLS. */
TEST(probe_cs_class_methods) {
NcpMetrics m =
ncp_metrics("Calc.cs", "namespace App {\n"
" class Calc {\n"
" private int offset;\n"
" public Calc(int offset) { this.offset = offset; }\n"
" public int Add(int x) { return offset + x; }\n"
" public int Double(int x) { return Add(Add(x)); }\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.methods >= 2); /* Add, Double */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* C#: interface + class implementing it. */
TEST(probe_cs_interface) {
NcpMetrics m = ncp_metrics(
"IRepo.cs", "namespace App {\n"
" interface IRepo<T> {\n"
" T Get(int id);\n"
" void Save(T item);\n"
" }\n\n"
" class MemRepo<T> : IRepo<T> {\n"
" private System.Collections.Generic.Dictionary<int, T> store = new();\n"
" public T Get(int id) => store[id];\n"
" public void Save(T item) { store[store.Count] = item; }\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
PASS();
}
/* C#: enum type. */
TEST(probe_cs_enum) {
NcpMetrics m = ncp_metrics(
"Status.cs",
"namespace App {\n"
" enum Status { Active, Inactive, Pending }\n\n"
" class StatusHelper {\n"
" public static bool IsActive(Status s) => s == Status.Active;\n"
" public static string Label(Status s) => IsActive(s) ? \"on\" : \"off\";\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.methods >= 1);
PASS();
}
/* C#: generic class. */
TEST(probe_cs_generic_class) {
NcpMetrics m = ncp_metrics("Box.cs", "namespace App {\n"
" class Box<T> {\n"
" private T value;\n"
" public Box(T v) { this.value = v; }\n"
" public T Unwrap() { return value; }\n"
" public Box<U> Map<U>(System.Func<T, U> f) {\n"
" return new Box<U>(f(Unwrap()));\n"
" }\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.methods >= 2);
PASS();
}
/* C#: struct type (value type). */
TEST(probe_cs_struct) {
NcpMetrics m = ncp_metrics(
"Point.cs",
"namespace App {\n"
" struct Point {\n"
" public float X, Y;\n"
" public Point(float x, float y) { X = x; Y = y; }\n"
" public float MagnitudeSq() { return X * X + Y * Y; }\n"
" public Point Add(Point other) { return new Point(X + other.X, Y + other.Y); }\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* struct Point */
ASSERT_TRUE(m.methods >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 10 — Ruby (grammar-only)
* Labels: Class:1, Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Ruby: class with methods + CALLS edge. */
TEST(probe_ruby_class_calls) {
NcpMetrics m = ncp_metrics("greeter.rb", "class Greeter\n"
" def initialize(name)\n"
" @name = name\n"
" end\n\n"
" def greet\n"
" format_greeting(@name)\n"
" end\n\n"
" private\n\n"
" def format_greeting(n)\n"
" \"Hello, #{n}!\"\n"
" end\n"
"end\n\n"
"def make_greeter(name)\n"
" Greeter.new(name)\n"
"end\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* class Greeter */
ASSERT_TRUE(m.functions >= 1);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Ruby: module definition creates a type-like node. */
TEST(probe_ruby_module) {
NcpMetrics m = ncp_metrics("utils.rb", "module MathUtils\n"
" def self.square(x)\n"
" x * x\n"
" end\n\n"
" def self.cube(x)\n"
" x * square(x)\n"
" end\n"
"end\n");
ASSERT_TRUE(m.ok);
/* A Module might be modelled as a Class or Function label. */
ASSERT_TRUE(m.types >= 1 || m.functions >= 1);
PASS();
}
/* Ruby: multiple top-level functions → Function nodes. */
TEST(probe_ruby_toplevel_functions) {
NcpMetrics m = ncp_metrics("calc.rb", "def add(a, b)\n a + b\nend\n\n"
"def mul(a, b)\n a * b\nend\n\n"
"def run\n add(mul(2, 3), 1)\nend\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 3); /* add, mul, run */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 11 — Swift (grammar-only)
* Labels: Class:1, Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Swift: struct + functions + CALLS edge. */
TEST(probe_swift_struct_calls) {
NcpMetrics m =
ncp_metrics("geo.swift", "struct Point {\n"
" var x: Double\n"
" var y: Double\n\n"
" func distanceTo(_ other: Point) -> Double {\n"
" let dx = x - other.x\n"
" let dy = y - other.y\n"
" return sqrt(dx*dx + dy*dy)\n"
" }\n"
"}\n\n"
"func origin() -> Point { return Point(x: 0, y: 0) }\n\n"
"func run() -> Double {\n"
" let p = origin()\n"
" return p.distanceTo(Point(x: 3, y: 4))\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.functions >= 1);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Swift: class with inheritance. */
TEST(probe_swift_class_inherit) {
NcpMetrics m = ncp_metrics(
"shape.swift", "class Shape {\n"
" func area() -> Double { return 0.0 }\n"
"}\n\n"
"class Circle: Shape {\n"
" let radius: Double\n"
" init(radius: Double) { self.radius = radius }\n"
" override func area() -> Double { return 3.14159 * radius * radius }\n"
"}\n\n"
"func totalArea(_ shapes: [Shape]) -> Double {\n"
" return shapes.reduce(0.0) { $0 + $1.area() }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
PASS();
}
/* Swift: enum with associated values. */
TEST(probe_swift_enum) {
NcpMetrics m =
ncp_metrics("result.swift", "enum AppError: Error {\n"
" case notFound(String)\n"
" case invalid\n"
"}\n\n"
"func lookup(_ key: String) -> Result<Int, AppError> {\n"
" if key == \"x\" { return .success(42) }\n"
" return .failure(.notFound(key))\n"
"}\n\n"
"func run() -> Int {\n"
" switch lookup(\"x\") {\n"
" case .success(let v): return v\n"
" case .failure: return -1\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 12 — Scala (grammar-only)
* Labels: Class:1, Function:1, Method:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Scala: object + class + methods. */
TEST(probe_scala_class_object) {
NcpMetrics m = ncp_metrics("calc.scala", "class Calc(base: Int) {\n"
" def add(x: Int): Int = base + x\n"
" def mul(x: Int): Int = base * x\n"
" def addThenMul(x: Int): Int = mul(add(x))\n"
"}\n\n"
"object CalcApp {\n"
" def main(args: Array[String]): Unit = {\n"
" val c = new Calc(10)\n"
" println(c.addThenMul(5))\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.functions + m.methods >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Scala: trait definition. */
TEST(probe_scala_trait) {
NcpMetrics m = ncp_metrics(
"greet.scala",
"trait Greeter {\n"
" def greet(name: String): String\n"
" def greetAll(names: List[String]): List[String] =\n"
" names.map(greet)\n"
"}\n\n"
"class PoliteGreeter extends Greeter {\n"
" def greet(name: String): String = s\"Good day, $name\"\n"
"}\n\n"
"def run(): List[String] = new PoliteGreeter().greetAll(List(\"Alice\", \"Bob\"))\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1);
ASSERT_TRUE(m.functions + m.methods >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 13 — Lua (grammar-only)
* Labels: Function:2, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Lua: top-level functions + CALLS edge. */
TEST(probe_lua_functions_calls) {
NcpMetrics m = ncp_metrics("math.lua", "local function square(x)\n"
" return x * x\n"
"end\n\n"
"local function hypotenuse(a, b)\n"
" return math.sqrt(square(a) + square(b))\n"
"end\n\n"
"function run(a, b)\n"
" return hypotenuse(a, b)\n"
"end\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2); /* square, hypotenuse (or run) */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Lua: table acting as class-like module — functions must appear. */
TEST(probe_lua_table_class) {
NcpMetrics m = ncp_metrics("counter.lua", "local Counter = {}\n"
"Counter.__index = Counter\n\n"
"function Counter.new(start)\n"
" local self = setmetatable({}, Counter)\n"
" self.value = start or 0\n"
" return self\n"
"end\n\n"
"function Counter:inc()\n"
" self.value = self.value + 1\n"
"end\n\n"
"function Counter:get()\n"
" return self.value\n"
"end\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 14 — Bash (grammar-only)
* Labels: Function:2, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Bash: function declarations + CALLS edge. */
TEST(probe_bash_functions_calls) {
NcpMetrics m = ncp_metrics("deploy.sh", "#!/usr/bin/env bash\n\n"
"log() {\n"
" echo \"[INFO] $*\"\n"
"}\n\n"
"build() {\n"
" log \"building...\"\n"
" make all\n"
"}\n\n"
"deploy() {\n"
" log \"deploying...\"\n"
" build\n"
"}\n\n"
"main() {\n"
" deploy \"$@\"\n"
"}\n\n"
"main \"$@\"\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 3); /* log, build, deploy, main */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Bash: function() { } syntax variant. */
TEST(probe_bash_function_keyword) {
NcpMetrics m = ncp_metrics("lib.sh", "function helper {\n"
" echo \"help: $1\"\n"
"}\n\n"
"function runner {\n"
" helper \"start\"\n"
" helper \"stop\"\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 15 — Dart (grammar-only)
* Labels: Class:1, Function:1, Module:1
*
* KNOWN GAP: CALLS edge NOT expected — extract_calls.c has no Dart branch.
* Node-creation (Function/Class nodes) IS expected to work.
* ══════════════════════════════════════════════════════════════════ */
/* Dart: top-level functions → Function nodes (GREEN expected). */
TEST(probe_dart_functions) {
NcpMetrics m = ncp_metrics("math.dart", "int square(int x) => x * x;\n\n"
"int cube(int x) => x * square(x);\n\n"
"void main() {\n"
" print(cube(3));\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2); /* square, cube (main may also appear) */
PASS();
}
/* Dart: class with methods → Class node + Function nodes (GREEN expected). */
TEST(probe_dart_class) {
NcpMetrics m = ncp_metrics("account.dart", "class Account {\n"
" String owner;\n"
" double balance;\n\n"
" Account(this.owner, {this.balance = 0.0});\n\n"
" void deposit(double amount) {\n"
" balance += amount;\n"
" }\n\n"
" bool withdraw(double amount) {\n"
" if (amount > balance) return false;\n"
" balance -= amount;\n"
" return true;\n"
" }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* class Account */
PASS();
}
/* Dart CALLS edge — KNOWN EXPECTED RED: extract_calls.c lacks Dart branch.
* Kept as a reproduction: turns GREEN when the fix is shipped. */
TEST(probe_dart_calls_edge_known_gap) {
NcpMetrics m = ncp_metrics("chain.dart", "void helper() {\n print('helper');\n}\n\n"
"void run() {\n helper();\n}\n");
ASSERT_TRUE(m.ok);
/* This assertion SHOULD fail until the Dart CALLS branch is added. */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 16 — Elixir (grammar-only)
* Labels: Class:1, Function:1, Module:1 (module -> Class)
* ══════════════════════════════════════════════════════════════════ */
/* Elixir: defmodule with multiple defs + CALLS edge. */
TEST(probe_elixir_module_calls) {
NcpMetrics m = ncp_metrics("calc.ex", "defmodule Calc do\n"
" def add(a, b), do: a + b\n\n"
" def mul(a, b), do: a * b\n\n"
" def add_then_mul(x, y) do\n"
" sum = add(x, y)\n"
" mul(sum, 2)\n"
" end\n"
"end\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1 || m.functions >= 1); /* module as Class or Function */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Elixir: nested module. */
TEST(probe_elixir_nested_module) {
NcpMetrics m = ncp_metrics("server.ex", "defmodule Server.Router do\n"
" def dispatch(path) do\n"
" handle(path)\n"
" end\n\n"
" defp handle(\"/health\"), do: :ok\n"
" defp handle(_), do: :not_found\n"
"end\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 17 — Haskell (grammar-only)
* Labels: Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Haskell: multiple top-level function definitions + CALLS edge. */
TEST(probe_haskell_functions_calls) {
NcpMetrics m = ncp_metrics("math.hs", "module Math where\n\n"
"square :: Int -> Int\n"
"square x = x * x\n\n"
"cube :: Int -> Int\n"
"cube x = x * square x\n\n"
"run :: Int -> Int\n"
"run n = cube n + square n\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1); /* at least one of square/cube/run */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Haskell: data type definition must not break function node extraction. */
TEST(probe_haskell_data_type) {
NcpMetrics m = ncp_metrics("tree.hs", "module Tree where\n\n"
"data Tree a = Leaf | Node (Tree a) a (Tree a)\n\n"
"insert :: Ord a => a -> Tree a -> Tree a\n"
"insert x Leaf = Node Leaf x Leaf\n"
"insert x (Node l v r)\n"
" | x < v = Node (insert x l) v r\n"
" | x > v = Node l v (insert x r)\n"
" | otherwise = Node l v r\n\n"
"depth :: Tree a -> Int\n"
"depth Leaf = 0\n"
"depth (Node l _ r) = 1 + max (depth l) (depth r)\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 18 — Zig (grammar-only)
* Labels: Function:2, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Zig: pub functions + CALLS edge. */
TEST(probe_zig_functions_calls) {
NcpMetrics m = ncp_metrics("math.zig", "const std = @import(\"std\");\n\n"
"fn square(x: i32) i32 {\n"
" return x * x;\n"
"}\n\n"
"pub fn sumSquares(a: i32, b: i32) i32 {\n"
" return square(a) + square(b);\n"
"}\n\n"
"pub fn main() void {\n"
" const result = sumSquares(3, 4);\n"
" std.debug.print(\"{d}\\n\", .{result});\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* Zig: struct with methods. */
TEST(probe_zig_struct_methods) {
NcpMetrics m = ncp_metrics("stack.zig", "const Stack = struct {\n"
" items: [64]i32 = undefined,\n"
" top: usize = 0,\n\n"
" pub fn push(self: *Stack, v: i32) void {\n"
" self.items[self.top] = v;\n"
" self.top += 1;\n"
" }\n\n"
" pub fn pop(self: *Stack) i32 {\n"
" self.top -= 1;\n"
" return self.items[self.top];\n"
" }\n"
"};\n\n"
"pub fn run() void {\n"
" var s = Stack{};\n"
" s.push(1);\n"
" _ = s.pop();\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1); /* run must be present */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 19 — OCaml (grammar-only)
* Labels: Function:2, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* OCaml: let bindings + application call. */
TEST(probe_ocaml_functions_calls) {
NcpMetrics m = ncp_metrics("math.ml", "let square x = x * x\n\n"
"let cube x = x * square x\n\n"
"let sum_squares a b = square a + square b\n\n"
"let () =\n"
" let _ = cube 3 in\n"
" let _ = sum_squares 3 4 in\n"
" ()\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 20 — Erlang (grammar-only)
* Labels: Function:2, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Erlang: module + exported functions + CALLS edge. */
TEST(probe_erlang_functions_calls) {
NcpMetrics m = ncp_metrics("math.erl", "-module(math).\n"
"-export([run/0, sum_squares/2]).\n\n"
"square(X) -> X * X.\n\n"
"sum_squares(A, B) -> square(A) + square(B).\n\n"
"run() ->\n"
" io:format(\"~p~n\", [sum_squares(3, 4)]).\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 21 — Groovy (grammar-only)
* Labels: Class:1, Method:1, Module:1
*
* KNOWN GAP: CALLS edge NOT expected — function_call callee resolution
* unhandled in extract_calls.c for Groovy.
* Node creation (Class/Method) IS expected to work.
* ══════════════════════════════════════════════════════════════════ */
/* Groovy: class + methods → Class + Method nodes (GREEN expected). */
TEST(probe_groovy_class_methods) {
NcpMetrics m = ncp_metrics("Calc.groovy", "class Calc {\n"
" int base\n\n"
" Calc(int base) { this.base = base }\n\n"
" int add(int x) { base + x }\n\n"
" int mul(int x) { base * x }\n"
"}\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.types >= 1); /* class Calc */
ASSERT_TRUE(m.methods >= 1);
PASS();
}
/* Groovy CALLS edge — KNOWN EXPECTED RED: callee extraction not handled.
* Kept as reproduction; turns GREEN when the fix ships. */
TEST(probe_groovy_calls_edge_known_gap) {
NcpMetrics m = ncp_metrics("funcs.groovy", "def helper() {\n println 'helping'\n}\n\n"
"def runner() {\n helper()\n}\n");
ASSERT_TRUE(m.ok);
/* This assertion SHOULD fail until Groovy callee extraction is added. */
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 23 — GDScript (grammar-only)
* Labels: Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* GDScript: func declarations + CALLS edge. */
TEST(probe_gdscript_functions_calls) {
NcpMetrics m = ncp_metrics("player.gd", "extends Node\n\n"
"var health: int = 100\n\n"
"func take_damage(amount: int) -> void:\n"
"\thealth -= amount\n"
"\tif health <= 0:\n"
"\t\tdie()\n\n"
"func die() -> void:\n"
"\tprint(\"died\")\n\n"
"func heal(amount: int) -> void:\n"
"\thealth = min(health + amount, 100)\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2); /* take_damage, die, heal */
ASSERT_TRUE(m.calls >= 1); /* take_damage -> die */
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 24 — Perl (grammar-only)
* Labels: Function:2, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Perl: sub declarations + CALLS edge. */
TEST(probe_perl_functions_calls) {
NcpMetrics m = ncp_metrics("math.pl", "#!/usr/bin/perl\n"
"use strict;\n"
"use warnings;\n\n"
"sub square {\n"
" my ($x) = @_;\n"
" return $x * $x;\n"
"}\n\n"
"sub sum_squares {\n"
" my ($a, $b) = @_;\n"
" return square($a) + square($b);\n"
"}\n\n"
"sub run {\n"
" print sum_squares(3, 4), \"\\n\";\n"
"}\n\n"
"run();\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 25 — R (grammar-only)
* Labels: Function:2, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* R: function assignments + CALLS edge. */
TEST(probe_r_functions_calls) {
NcpMetrics m = ncp_metrics("math.R", "square <- function(x) {\n"
" x * x\n"
"}\n\n"
"sum_squares <- function(a, b) {\n"
" square(a) + square(b)\n"
"}\n\n"
"run <- function() {\n"
" cat(sum_squares(3, 4), \"\\n\")\n"
"}\n\n"
"run()\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 2);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* GROUP 26 — Fortran (grammar-only)
* Labels: Function:1, Module:1
* ══════════════════════════════════════════════════════════════════ */
/* Fortran: function + subroutine + call. */
TEST(probe_fortran_functions_calls) {
NcpMetrics m = ncp_metrics("math.f90", "function square(x) result(y)\n"
" integer, intent(in) :: x\n"
" integer :: y\n"
" y = x * x\n"
"end function square\n\n"
"function sum_sq(a, b) result(total)\n"
" integer, intent(in) :: a, b\n"
" integer :: total\n"
" total = square(a) + square(b)\n"
"end function sum_sq\n");
ASSERT_TRUE(m.ok);
ASSERT_TRUE(m.functions >= 1);
ASSERT_TRUE(m.calls >= 1);
PASS();
}
/* ══════════════════════════════════════════════════════════════════
* SUITE REGISTRATION
* ══════════════════════════════════════════════════════════════════ */
SUITE(node_creation_probe) {
/* Go (hybrid LSP) */
RUN_TEST(probe_go_functions);
RUN_TEST(probe_go_calls_edge);
RUN_TEST(probe_go_variadic_named_return);
RUN_TEST(probe_go_method_on_struct);
RUN_TEST(probe_go_interface_node);
RUN_TEST(probe_go_closure);
RUN_TEST(probe_go_generic_function);
/* C (hybrid LSP) */
RUN_TEST(probe_c_functions);
RUN_TEST(probe_c_calls_edge);
RUN_TEST(probe_c_function_pointer);
RUN_TEST(probe_c_inline_function);
RUN_TEST(probe_c_struct_node);
/* C++ (hybrid LSP) */
RUN_TEST(probe_cpp_class_methods);
RUN_TEST(probe_cpp_calls_edge);
RUN_TEST(probe_cpp_template_function);
RUN_TEST(probe_cpp_enum_class);
RUN_TEST(probe_cpp_struct_operator);
RUN_TEST(probe_cpp_inheritance);
/* Rust (hybrid LSP) */
RUN_TEST(probe_rust_struct_impl);
RUN_TEST(probe_rust_enum);
RUN_TEST(probe_rust_trait_impl);
RUN_TEST(probe_rust_calls_edge);
RUN_TEST(probe_rust_closure);
RUN_TEST(probe_rust_type_alias);
/* Python (hybrid LSP) */
RUN_TEST(probe_python_functions);
RUN_TEST(probe_python_class);
RUN_TEST(probe_python_nested_function);
RUN_TEST(probe_python_decorator);
RUN_TEST(probe_python_lambda);
RUN_TEST(probe_python_dataclass);
/* TypeScript (hybrid LSP) */
RUN_TEST(probe_ts_interface_class);
RUN_TEST(probe_ts_generic_arrow);
RUN_TEST(probe_ts_enum);
RUN_TEST(probe_ts_calls_edge);
RUN_TEST(probe_ts_class_extends);
RUN_TEST(probe_ts_type_alias);
/* Java (hybrid LSP) */
RUN_TEST(probe_java_class_methods);
RUN_TEST(probe_java_interface);
RUN_TEST(probe_java_enum);
RUN_TEST(probe_java_inner_class);
RUN_TEST(probe_java_lambda);
/* Kotlin (hybrid LSP) */
RUN_TEST(probe_kotlin_data_class);
RUN_TEST(probe_kotlin_sealed_class);
RUN_TEST(probe_kotlin_interface_lambda);
RUN_TEST(probe_kotlin_calls_edge);
RUN_TEST(probe_kotlin_enum);
/* C# (hybrid LSP) */
RUN_TEST(probe_cs_class_methods);
RUN_TEST(probe_cs_interface);
RUN_TEST(probe_cs_enum);
RUN_TEST(probe_cs_generic_class);
RUN_TEST(probe_cs_struct);
/* Ruby (grammar-only) */
RUN_TEST(probe_ruby_class_calls);
RUN_TEST(probe_ruby_module);
RUN_TEST(probe_ruby_toplevel_functions);
/* Swift (grammar-only) */
RUN_TEST(probe_swift_struct_calls);
RUN_TEST(probe_swift_class_inherit);
RUN_TEST(probe_swift_enum);
/* Scala (grammar-only) */
RUN_TEST(probe_scala_class_object);
RUN_TEST(probe_scala_trait);
/* Lua (grammar-only) */
RUN_TEST(probe_lua_functions_calls);
RUN_TEST(probe_lua_table_class);
/* Bash (grammar-only) */
RUN_TEST(probe_bash_functions_calls);
RUN_TEST(probe_bash_function_keyword);
/* Dart (grammar-only; CALLS gap is a known-red reproduction) */
RUN_TEST(probe_dart_functions);
RUN_TEST(probe_dart_class);
RUN_TEST(probe_dart_calls_edge_known_gap);
/* Elixir (grammar-only) */
RUN_TEST(probe_elixir_module_calls);
RUN_TEST(probe_elixir_nested_module);
/* Haskell (grammar-only) */
RUN_TEST(probe_haskell_functions_calls);
RUN_TEST(probe_haskell_data_type);
/* Zig (grammar-only) */
RUN_TEST(probe_zig_functions_calls);
RUN_TEST(probe_zig_struct_methods);
/* OCaml (grammar-only) */
RUN_TEST(probe_ocaml_functions_calls);
/* Erlang (grammar-only) */
RUN_TEST(probe_erlang_functions_calls);
/* Groovy (grammar-only; CALLS gap is a known-red reproduction) */
RUN_TEST(probe_groovy_class_methods);
RUN_TEST(probe_groovy_calls_edge_known_gap);
/* Nim (grammar-only) */
/* GDScript (grammar-only) */
RUN_TEST(probe_gdscript_functions_calls);
/* Perl (grammar-only) */
RUN_TEST(probe_perl_functions_calls);
/* R (grammar-only) */
RUN_TEST(probe_r_functions_calls);
/* Fortran (grammar-only) */
RUN_TEST(probe_fortran_functions_calls);
}