diff --git a/src/pipeline/pass_definitions.c b/src/pipeline/pass_definitions.c index 804ffba659..08c69bbb83 100644 --- a/src/pipeline/pass_definitions.c +++ b/src/pipeline/pass_definitions.c @@ -337,7 +337,7 @@ static void process_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *def, const * (SQL FROM/JOIN lineage). pass_parallel.c and pipeline_incremental.c seed * through the same predicate, so the three registries cannot diverge. */ if (node_id > 0 && cbm_label_is_registry_symbol(def->label)) { - cbm_registry_add(ctx->registry, def->name, def->qualified_name, def->label); + cbm_registry_add(ctx->registry, def->name, def->qualified_name, def->label, def->is_test); } char *file_qn = cbm_pipeline_fqn_compute(ctx->project_name, rel, "__file__"); const cbm_gbuf_node_t *file_node = cbm_gbuf_find_by_qn(ctx->gbuf, file_qn); diff --git a/src/pipeline/pass_parallel.c b/src/pipeline/pass_parallel.c index 9df46c1af2..31f6aa933d 100644 --- a/src/pipeline/pass_parallel.c +++ b/src/pipeline/pass_parallel.c @@ -1675,7 +1675,7 @@ static int register_and_link_def(cbm_pipeline_ctx_t *ctx, const CBMDefinition *d /* Registry membership is defined ONCE by cbm_label_is_registry_symbol * (helpers.c) — see pass_definitions.c for the per-label rationale. */ if (cbm_label_is_registry_symbol(def->label)) { - cbm_registry_add(ctx->registry, def->name, def->qualified_name, def->label); + cbm_registry_add(ctx->registry, def->name, def->qualified_name, def->label, def->is_test); (*reg_entries)++; } const cbm_gbuf_node_t *def_node = cbm_gbuf_find_by_qn(ctx->gbuf, def->qualified_name); diff --git a/src/pipeline/pass_tests.c b/src/pipeline/pass_tests.c index 9cade02a0d..65c099bf08 100644 --- a/src/pipeline/pass_tests.c +++ b/src/pipeline/pass_tests.c @@ -44,7 +44,7 @@ static const char *itoa_log(int val) { } /* Check if a node has is_test:true in its properties JSON. */ -static bool node_is_test(const cbm_gbuf_node_t *n) { +bool cbm_node_is_test(const cbm_gbuf_node_t *n) { if (!n || !n->properties_json) { return false; } @@ -227,13 +227,13 @@ static int create_tests_edges(cbm_pipeline_ctx_t *ctx) { } bool src_is_test = - node_is_test(src) || (src->file_path && cbm_is_test_path(src->file_path)); + cbm_node_is_test(src) || (src->file_path && cbm_is_test_path(src->file_path)); if (!src_is_test) { continue; } bool tgt_is_test = - node_is_test(tgt) || (tgt->file_path && cbm_is_test_path(tgt->file_path)); + cbm_node_is_test(tgt) || (tgt->file_path && cbm_is_test_path(tgt->file_path)); if (tgt_is_test) { continue; } diff --git a/src/pipeline/pipeline.h b/src/pipeline/pipeline.h index 0ec6d78729..60df495286 100644 --- a/src/pipeline/pipeline.h +++ b/src/pipeline/pipeline.h @@ -236,8 +236,11 @@ cbm_registry_t *cbm_registry_new(void); void cbm_registry_free(cbm_registry_t *r); /* Register a function/method/class. All strings are copied. */ +/* `is_test` is the extractor's verdict for this symbol (CBMDefinition.is_test: + * the file's test-ness OR the definition's own test attributes). The registry + * stores it and scores with it; it does not derive test-ness from the QN. */ void cbm_registry_add(cbm_registry_t *r, const char *name, const char *qualified_name, - const char *label); + const char *label, bool is_test); /* Resolve a callee name using prioritized strategies. * import_map: NULL-terminated array of {local_name, resolved_qn} pairs, or NULL. diff --git a/src/pipeline/pipeline_incremental.c b/src/pipeline/pipeline_incremental.c index 154518658d..cc90c31941 100644 --- a/src/pipeline/pipeline_incremental.c +++ b/src/pipeline/pipeline_incremental.c @@ -1041,7 +1041,7 @@ static void registry_visitor(const cbm_gbuf_node_t *node, void *userdata) { if (!incr_label_is_registry_symbol(node->label)) { return; } - cbm_registry_add(r, node->name, node->qualified_name, node->label); + cbm_registry_add(r, node->name, node->qualified_name, node->label, cbm_node_is_test(node)); } static void free_incremental_result_cache(CBMFileResult **cache, int count) { diff --git a/src/pipeline/pipeline_internal.h b/src/pipeline/pipeline_internal.h index 28daa54346..f83b6cb6e0 100644 --- a/src/pipeline/pipeline_internal.h +++ b/src/pipeline/pipeline_internal.h @@ -169,6 +169,12 @@ typedef struct { typedef bool (*cbm_result_want_fn)(const CBMFileResult *header); CBMFileResult *cbm_pipeline_result_acquire(const cbm_pipeline_ctx_t *ctx, CBMFileResult **cache, int i, cbm_result_want_fn want, bool *loaded); +/* True when a graph-buffer node carries is_test:true in its properties JSON. + * The incremental registry seed has only the persisted node, not the + * CBMDefinition the definition passes hand to cbm_registry_add, and both must + * give the registry the same verdict for the same symbol. */ +bool cbm_node_is_test(const cbm_gbuf_node_t *n); + void cbm_pipeline_result_release(CBMFileResult *r, bool loaded); /* Log the store counters, close and delete the store, drop the latch. */ diff --git a/src/pipeline/registry.c b/src/pipeline/registry.c index bdf6f5944d..c0db4d71fb 100644 --- a/src/pipeline/registry.c +++ b/src/pipeline/registry.c @@ -28,6 +28,7 @@ enum { REG_MAX_CANDIDATES = 256 }; #include "cbm.h" /* cbm_label_is_relation — the resolve-time relation veto */ #include "foundation/compat.h" /* CBM_TLS */ #include "foundation/hash_table.h" +#include "foundation/log.h" #include "foundation/dyn_array.h" #include "foundation/platform.h" @@ -72,16 +73,16 @@ const char *cbm_confidence_band(double score) { /* ── Internal types ──────────────────────────────────────────────── */ -/* Array of QN strings for byName index, with the test/mock verdict of each - * name cached beside it: the scorer asked is_test_qn for every candidate of - * every unresolved call, and the answer depends only on the name, which never - * changes once registered (waste sanitizer scaling lane, 2026-09-17). Field - * order and names match CBM_DYN_ARRAY(char *) so cbm_da_push works on it. */ +/* Array of QN strings for byName index, with each entry's test verdict beside + * it. The verdict is the extractor's, handed to cbm_registry_add by the caller + * that already computed it from the file path and the definition's attributes; + * it is stored rather than derived, so every entry always has one. Field order + * and names match CBM_DYN_ARRAY(char *) so cbm_da_push works on it. */ typedef struct { char **items; int count; int cap; - uint8_t *is_test; /* is_test_qn(items[i]), one byte per entry */ + uint8_t *is_test; /* the caller's verdict for items[i], one byte per entry */ int is_test_cap; } qn_array_t; @@ -151,78 +152,19 @@ static int common_prefix_len(const char *a, const char *b) { enum { REG_TEST_PENALTY = 1000 }; -/* Check if a qualified name looks like a test/mock path: does it contain any of - * Test, test, Mock, mock, Stub, stub, Fake, fake, Fixture, spec? One pass that - * dispatches on the first character instead of ten strstr scans of the whole - * name (this ran for every candidate of every unresolved call: the scaling - * lane's x4.0, waste sanitizer 2026-09-17). */ -static bool is_test_qn(const char *qn) { - if (!qn) { - return false; - } - for (const char *p = qn; *p; p++) { - switch (*p) { - case 'T': - if (strncmp(p, "Test", 4) == 0) { - return true; - } - break; - case 't': - if (strncmp(p, "test", 4) == 0) { - return true; - } - break; - case 'M': - if (strncmp(p, "Mock", 4) == 0) { - return true; - } - break; - case 'm': - if (strncmp(p, "mock", 4) == 0) { - return true; - } - break; - case 'S': - if (strncmp(p, "Stub", 4) == 0) { - return true; - } - break; - case 's': - if (strncmp(p, "stub", 4) == 0 || strncmp(p, "spec", 4) == 0) { - return true; - } - break; - case 'F': - if (strncmp(p, "Fake", 4) == 0 || strncmp(p, "Fixture", 7) == 0) { - return true; - } - break; - case 'f': - if (strncmp(p, "fake", 4) == 0) { - return true; - } - break; - default: - break; - } - } - return false; -} - -/* The cached test verdicts, only when they cover every entry: a failed growth - * leaves the cache short, and then the scorer asks per candidate as before. */ +/* The stored test verdicts. index_under_name drops an entry it cannot record a + * verdict for, so this covers every entry of a non-empty bucket. */ static const uint8_t *qn_test_flags(const qn_array_t *arr) { - return arr->is_test && arr->is_test_cap >= arr->count ? arr->is_test : NULL; + return arr->is_test; } /* Score a candidate for tiebreaking. Higher = better. * Layer 1: Non-test code preferred over test code (+1000) * Layer 2: Namespace proximity via common prefix length (+plen) - * `is_test` is the cached verdict when the caller has one (-1: ask). */ -static int candidate_score(const char *candidate_qn, const char *module_qn, int is_test) { + * `is_test` is the verdict stored for this candidate at registration. */ +static int candidate_score(const char *candidate_qn, const char *module_qn, bool is_test) { int score = 0; - bool test = is_test >= 0 ? is_test != 0 : is_test_qn(candidate_qn); - if (!test) { + if (!is_test) { score += REG_TEST_PENALTY; } score += common_prefix_len(candidate_qn, module_qn); @@ -268,8 +210,7 @@ static const char *best_by_import_distance(const char **candidates, const uint8_ const char *best = NULL; int best_score = CBM_NOT_FOUND; for (int i = 0; i < count; i++) { - int score = - candidate_score(candidates[i], module_qn, is_test_flags ? (int)is_test_flags[i] : -1); + int score = candidate_score(candidates[i], module_qn, is_test_flags[i] != 0); if (score > best_score || (score == best_score && best && candidate_outranks_on_tie(candidates[i], best))) { best_score = score; @@ -863,7 +804,8 @@ void cbm_registry_free(cbm_registry_t *r) { * * No array dedup needed: cbm_registry_add's exact-map check guarantees the QN * is new, and it calls this at most once per distinct key. */ -static void index_under_name(cbm_registry_t *r, const char *key, const char *owned_qn) { +static void index_under_name(cbm_registry_t *r, const char *key, const char *owned_qn, + bool is_test) { qn_array_t *arr = cbm_ht_get(r->by_name, key); if (!arr) { arr = calloc(CBM_ALLOC_ONE, sizeof(qn_array_t)); @@ -872,7 +814,7 @@ static void index_under_name(cbm_registry_t *r, const char *key, const char *own int before = arr->count; cbm_da_push(arr, (char *)owned_qn); if (arr->count == before) { - return; /* the name could not be recorded: no verdict to cache */ + return; /* the name could not be recorded: no verdict to store */ } if (arr->count > arr->is_test_cap) { int want = arr->cap > 0 ? arr->cap : arr->count; @@ -883,13 +825,19 @@ static void index_under_name(cbm_registry_t *r, const char *key, const char *own arr->is_test_cap = want; } } - if (arr->count <= arr->is_test_cap) { - arr->is_test[arr->count - SKIP_ONE] = is_test_qn(owned_qn) ? 1 : 0; + if (arr->count > arr->is_test_cap) { + /* The verdict can no longer be recomputed from the QN, so an entry + * without one would be scored as non-test and outrank real code. Drop + * it instead: one missing candidate beats a wrong winner. */ + arr->count = before; + cbm_log_warn("registry.verdict_alloc_failed", "key", key); + return; } + arr->is_test[arr->count - SKIP_ONE] = is_test ? 1 : 0; } void cbm_registry_add(cbm_registry_t *r, const char *name, const char *qualified_name, - const char *label) { + const char *label, bool is_test) { if (!r || !qualified_name || !label) { return; } @@ -945,14 +893,14 @@ void cbm_registry_add(cbm_registry_t *r, const char *name, const char *qualified * `name` is NULL or empty only for callers that have no symbol name to * give; those have the derived key and nothing else. */ const char *derived = simple_name(qualified_name); - index_under_name(r, derived, owned_qn); + index_under_name(r, derived, owned_qn, is_test); /* '#' is a QN fence, and extract_defs.c's rust_cfg_qualified_name is the * only thing in the tree that mints one today. A grammar that starts * minting a '#' opts into this second key by doing so, whatever it means by * the fence: its symbols become reachable under the passed name as well, * and they share that name's bucket with everything else filed under it. */ if (name && name[0] && strchr(derived, '#') && strcmp(name, derived) != 0) { - index_under_name(r, name, owned_qn); + index_under_name(r, name, owned_qn, is_test); } } @@ -1111,9 +1059,7 @@ static cbm_resolution_t resolve_multi_with_imports(const qn_array_t *arr, const int fcount = 0; for (int i = 0; i < arr->count && fcount < CBM_SZ_256; i++) { if (is_import_reachable(arr->items[i], import_vals, import_count)) { - if (flags) { - filtered_test[fcount] = flags[i]; - } + filtered_test[fcount] = flags[i]; filtered[fcount] = arr->items[i]; fcount++; } @@ -1123,8 +1069,7 @@ static cbm_resolution_t resolve_multi_with_imports(const qn_array_t *arr, const return (cbm_resolution_t){filtered[0], "suffix_match", conf, arr->count}; } if (fcount > SKIP_ONE) { - const char *best = - best_by_import_distance(filtered, flags ? filtered_test : NULL, fcount, module_qn); + const char *best = best_by_import_distance(filtered, filtered_test, fcount, module_qn); if (best) { double conf = candidate_count_penalty(CONF_SUFFIX_MATCH, fcount); return (cbm_resolution_t){best, "suffix_match", conf, fcount}; @@ -1454,11 +1399,15 @@ cbm_resolution_t cbm_registry_resolve_lineage(const cbm_registry_t *r, const cha /* ── Fuzzy Resolve ──────────────────────────────────────────────── */ /* Filter candidates by import reachability. Returns count of reachable. */ -static int filter_import_reachable(const char **candidates, int count, const char **import_vals, - int import_count, const char **out, int max_out) { +/* Keeps `out_flags` in step with `out`: the verdicts are stored per entry and + * cannot be recomputed from the QN, so a filtered view has to carry its own. */ +static int filter_import_reachable(const char **candidates, const uint8_t *flags, int count, + const char **import_vals, int import_count, const char **out, + uint8_t *out_flags, int max_out) { int n = 0; for (int i = 0; i < count && n < max_out; i++) { if (is_import_reachable(candidates[i], import_vals, import_count)) { + out_flags[n] = flags[i]; out[n++] = candidates[i]; } } @@ -1498,13 +1447,17 @@ cbm_fuzzy_result_t cbm_registry_fuzzy_resolve(const cbm_registry_t *r, const cha /* Multiple candidates: filter by import reachability */ const char *filtered[CBM_SZ_256]; + uint8_t filtered_flags[CBM_SZ_256]; int fcount = arr->count; const char **fptr = (const char **)arr->items; + const uint8_t *fflags = qn_test_flags(arr); if (have_imports) { - fcount = filter_import_reachable((const char **)arr->items, arr->count, import_map_vals, - import_map_count, filtered, CBM_SZ_256); + fcount = filter_import_reachable((const char **)arr->items, qn_test_flags(arr), arr->count, + import_map_vals, import_map_count, filtered, + filtered_flags, CBM_SZ_256); fptr = filtered; + fflags = filtered_flags; } if (fcount == 0) { @@ -1524,8 +1477,7 @@ cbm_fuzzy_result_t cbm_registry_fuzzy_resolve(const cbm_registry_t *r, const cha {fptr[0], "fuzzy", candidate_count_penalty(CONF_FUZZY_SINGLE, arr->count), arr->count}, true}; } - const char *best = best_by_import_distance( - fptr, fptr == (const char **)arr->items ? qn_test_flags(arr) : NULL, fcount, module_qn); + const char *best = best_by_import_distance(fptr, fflags, fcount, module_qn); if (!best) { return no_match; } diff --git a/tests/test_pipeline.c b/tests/test_pipeline.c index 03a5cad70a..a39422fb71 100644 --- a/tests/test_pipeline.c +++ b/tests/test_pipeline.c @@ -11305,8 +11305,8 @@ TEST(helm_parse_chart_no_deps_issue338) { TEST(registry_resolve_single_candidate) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); - cbm_registry_add(reg, "ValidateOrder", "svcB.validators.ValidateOrder", "Function"); + cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); + cbm_registry_add(reg, "ValidateOrder", "svcB.validators.ValidateOrder", "Function", false); /* Normal resolve unique name */ cbm_resolution_t r = cbm_registry_resolve(reg, "CreateOrder", "svcC.caller", NULL, NULL, 0); @@ -11324,7 +11324,7 @@ TEST(registry_resolve_single_candidate) { TEST(registry_fuzzy_nonexistent) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); + cbm_registry_add(reg, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "NonExistent", "svcC.caller", NULL, NULL, 0); @@ -11336,8 +11336,8 @@ TEST(registry_fuzzy_nonexistent) { TEST(registry_fuzzy_multiple_best_by_distance) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "svcA.handlers.Process", "Function"); - cbm_registry_add(reg, "Process", "svcB.handlers.Process", "Function"); + cbm_registry_add(reg, "Process", "svcA.handlers.Process", "Function", false); + cbm_registry_add(reg, "Process", "svcB.handlers.Process", "Function", false); /* Caller in svcA → prefer svcA */ cbm_fuzzy_result_t fr = @@ -11356,7 +11356,7 @@ TEST(registry_fuzzy_multiple_best_by_distance) { TEST(registry_fuzzy_simple_name_extraction) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "DoWork", "myproject.utils.DoWork", "Function"); + cbm_registry_add(reg, "DoWork", "myproject.utils.DoWork", "Function", false); /* Deeply qualified name → extract "DoWork" */ cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "some.deep.module.DoWork", @@ -11381,8 +11381,8 @@ TEST(registry_fuzzy_empty) { TEST(registry_exists) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "pkg.module.Foo", "Function"); - cbm_registry_add(reg, "Bar", "pkg.module.Bar", "Method"); + cbm_registry_add(reg, "Foo", "pkg.module.Foo", "Function", false); + cbm_registry_add(reg, "Bar", "pkg.module.Bar", "Method", false); ASSERT_TRUE(cbm_registry_exists(reg, "pkg.module.Foo")); ASSERT_TRUE(cbm_registry_exists(reg, "pkg.module.Bar")); @@ -11395,7 +11395,7 @@ TEST(registry_exists) { TEST(registry_confidence_import_map) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function"); + cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function", false); const char *keys[] = {"other"}; const char *vals[] = {"proj.other"}; @@ -11410,7 +11410,7 @@ TEST(registry_confidence_import_map) { TEST(registry_confidence_import_map_suffix) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.other.sub.Foo", "Function"); + cbm_registry_add(reg, "Foo", "proj.other.sub.Foo", "Function", false); const char *keys[] = {"other"}; const char *vals[] = {"proj.other"}; @@ -11425,7 +11425,7 @@ TEST(registry_confidence_import_map_suffix) { TEST(registry_confidence_same_module) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function"); + cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Foo", "proj.pkg", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.pkg.Foo"); @@ -11438,7 +11438,7 @@ TEST(registry_confidence_same_module) { TEST(registry_confidence_unique_name) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function"); + cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Bar", "proj.unrelated", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.pkg.Bar"); @@ -11451,8 +11451,8 @@ TEST(registry_confidence_unique_name) { TEST(registry_confidence_suffix_match) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Process", "proj.svcA.caller", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.svcA.Process"); @@ -11468,7 +11468,7 @@ TEST(registry_confidence_suffix_match) { * wrong target — and with one candidate it won the top name-only confidence. */ TEST(registry_receiver_chain_refuses_library_unique_name_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable"); + cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable", false); cbm_resolution_t r = cbm_registry_resolve(reg, "URLSession.shared.data", "HomeboxUI.Net", NULL, NULL, 0); @@ -11482,8 +11482,8 @@ TEST(registry_receiver_chain_refuses_library_unique_name_issue1893) { * the final name and import distance picks the winner. */ TEST(registry_receiver_chain_refuses_library_suffix_match_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable"); - cbm_registry_add(reg, "data", "HomeboxUI.Payload.data", "Variable"); + cbm_registry_add(reg, "data", "HomeboxUI.PickedFile.data", "Variable", false); + cbm_registry_add(reg, "data", "HomeboxUI.Payload.data", "Variable", false); cbm_resolution_t r = cbm_registry_resolve(reg, "URLSession.shared.data", "HomeboxUI.Net", NULL, NULL, 0); @@ -11497,7 +11497,7 @@ TEST(registry_receiver_chain_refuses_library_suffix_match_issue1893) { * so Calendar really is in the receiver chain. */ TEST(registry_receiver_chain_keeps_project_extension_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "startOfDayUTC", "AuthDTOs.Calendar.startOfDayUTC", "Method"); + cbm_registry_add(reg, "startOfDayUTC", "AuthDTOs.Calendar.startOfDayUTC", "Method", false); cbm_resolution_t r = cbm_registry_resolve(reg, "Calendar.utcGregorian.startOfDayUTC", "HomeboxUI.Stats", NULL, NULL, 0); @@ -11517,7 +11517,7 @@ TEST(registry_receiver_chain_keeps_project_extension_issue1893) { TEST(registry_receiver_chain_admits_factory_chain_into_nested_type) { cbm_registry_t *reg = cbm_registry_new(); cbm_registry_add(reg, "putList", "org.elasticsearch.common.settings.Settings.Builder.putList", - "Method"); + "Method", false); cbm_resolution_t r = cbm_registry_resolve( reg, "Settings.builder().putList", "org.elasticsearch.index.IndexSettings", NULL, NULL, 0); @@ -11528,7 +11528,7 @@ TEST(registry_receiver_chain_admits_factory_chain_into_nested_type) { * .toBuilder().setWarningsHandler -> RequestOptions.Builder.setWarningsHandler. */ cbm_registry_add(reg, "setWarningsHandler", "org.elasticsearch.client.RequestOptions.Builder.setWarningsHandler", - "Method"); + "Method", false); r = cbm_registry_resolve(reg, "RequestOptions.DEFAULT.toBuilder().setWarningsHandler", "org.elasticsearch.client.Rest", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, @@ -11544,7 +11544,7 @@ TEST(registry_receiver_chain_admits_factory_chain_into_nested_type) { TEST(registry_receiver_chain_still_refuses_foreign_root_with_ancestry_rule) { cbm_registry_t *reg = cbm_registry_new(); cbm_registry_add(reg, "getEncoder", "org.elasticsearch.index.codec.DocOffsetsCodec.getEncoder", - "Method"); + "Method", false); cbm_resolution_t r = cbm_registry_resolve( reg, "Base64.getEncoder", "org.elasticsearch.index.IndexSettings", NULL, NULL, 0); @@ -11554,7 +11554,7 @@ TEST(registry_receiver_chain_still_refuses_foreign_root_with_ancestry_rule) { * org.elasticsearch.common.Math.toIntExact through "Math" appearing as an * ancestry segment ONLY IF that segment really is the type — here it is, and * that is the correct outcome (the project's own Math.toIntExact). */ - cbm_registry_add(reg, "toIntExact", "org.elasticsearch.common.Math.toIntExact", "Method"); + cbm_registry_add(reg, "toIntExact", "org.elasticsearch.common.Math.toIntExact", "Method", false); r = cbm_registry_resolve(reg, "Math.toIntExact", "org.elasticsearch.index.IndexSettings", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "org.elasticsearch.common.Math.toIntExact"); @@ -11567,7 +11567,7 @@ TEST(registry_receiver_chain_still_refuses_foreign_root_with_ancestry_rule) { * must not look at it, or every ordinary vm.load style call would be refused. */ TEST(registry_receiver_chain_ignores_lowercase_root_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "load", "HomeboxUI.EntityListViewModel.load", "Method"); + cbm_registry_add(reg, "load", "HomeboxUI.EntityListViewModel.load", "Method", false); cbm_resolution_t r = cbm_registry_resolve(reg, "vm.load", "HomeboxUI.Views", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "HomeboxUI.EntityListViewModel.load"); @@ -11580,7 +11580,7 @@ TEST(registry_receiver_chain_ignores_lowercase_root_issue1893) { /* An unqualified callee has no chain at all and must pass through unchanged. */ TEST(registry_receiver_chain_ignores_bare_name_issue1893) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "helper", "proj.pkg.helper", "Function"); + cbm_registry_add(reg, "helper", "proj.pkg.helper", "Function", false); cbm_resolution_t r = cbm_registry_resolve(reg, "helper", "proj.other", NULL, NULL, 0); ASSERT_STR_EQ(r.qualified_name, "proj.pkg.helper"); @@ -11592,7 +11592,7 @@ TEST(registry_receiver_chain_ignores_bare_name_issue1893) { TEST(registry_fuzzy_confidence_single) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Handler", "proj.svc.Handler", "Function"); + cbm_registry_add(reg, "Handler", "proj.svc.Handler", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "unknownPkg.Handler", "proj.caller", NULL, NULL, 0); @@ -11606,8 +11606,8 @@ TEST(registry_fuzzy_confidence_single) { TEST(registry_fuzzy_confidence_distance) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(reg, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(reg, "Process", "proj.svcB.Process", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(reg, "unknownPkg.Process", "proj.svcA.other", NULL, NULL, 0); @@ -11621,8 +11621,8 @@ TEST(registry_fuzzy_confidence_distance) { TEST(registry_negative_import_rejects) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Process", "proj.billing.Process", "Function"); - cbm_registry_add(reg, "Process", "proj.handler.Process", "Function"); + cbm_registry_add(reg, "Process", "proj.billing.Process", "Function", false); + cbm_registry_add(reg, "Process", "proj.handler.Process", "Function", false); /* Import only handler's module → should prefer handler */ const char *keys[] = {"handler"}; @@ -11636,7 +11636,7 @@ TEST(registry_negative_import_rejects) { TEST(registry_fuzzy_import_penalty) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function", false); /* Has imports but billing not imported → confidence halved */ const char *keys[] = {"other"}; @@ -11653,7 +11653,7 @@ TEST(registry_fuzzy_import_penalty) { TEST(registry_fuzzy_no_import_map_passthrough) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(reg, "Handler", "proj.billing.Handler", "Function", false); /* NULL import map → no penalty, full fuzzy confidence */ cbm_fuzzy_result_t fr = @@ -11667,10 +11667,10 @@ TEST(registry_fuzzy_no_import_map_passthrough) { TEST(registry_find_by_name) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function"); - cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function"); - cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function"); - cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method"); + cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function", false); + cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function", false); + cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function", false); + cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method", false); /* FindByName returns all entries for "Foo" */ const char **foos = NULL; @@ -13003,10 +13003,10 @@ TEST(registry_is_import_reachable) { /* Port of FindEndingWith portion from Go TestFunctionRegistry in pipeline_test.go */ TEST(registry_find_ending_with) { cbm_registry_t *reg = cbm_registry_new(); - cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function"); - cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function"); - cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function"); - cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method"); + cbm_registry_add(reg, "Foo", "proj.pkg.Foo", "Function", false); + cbm_registry_add(reg, "Bar", "proj.pkg.Bar", "Function", false); + cbm_registry_add(reg, "Foo", "proj.other.Foo", "Function", false); + cbm_registry_add(reg, "transform", "proj.utils.DataProcessor.transform", "Method", false); /* FindEndingWith "DataProcessor.transform" → 1 match */ const char **matches = NULL; diff --git a/tests/test_registry.c b/tests/test_registry.c index f77b1460bb..beb125024a 100644 --- a/tests/test_registry.c +++ b/tests/test_registry.c @@ -191,7 +191,7 @@ TEST(registry_free_null) { TEST(registry_add_and_exists) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); ASSERT_EQ(cbm_registry_size(r), 1); ASSERT_TRUE(cbm_registry_exists(r, "proj.cmd.main")); ASSERT_FALSE(cbm_registry_exists(r, "proj.cmd.other")); @@ -199,10 +199,52 @@ TEST(registry_add_and_exists) { PASS(); } +/* REG_TEST_PENALTY had no direct coverage. Of the registry call sites in this + * suite, none registered a symbol the scorer would treat as a test, so the + * layer that keeps production code ahead of test code was never exercised. + * These three pin it, and pin where the verdict comes from. */ +TEST(registry_prefers_non_test_over_test_candidate) { + cbm_registry_t *r = cbm_registry_new(); + cbm_registry_add(r, "helper", "proj.app.helper", "Function", false); + cbm_registry_add(r, "helper", "proj.harness.helper", "Function", true); + + cbm_resolution_t res = cbm_registry_resolve(r, "helper", "proj.other", NULL, NULL, 0); + ASSERT_STR_EQ(res.qualified_name, "proj.app.helper"); + cbm_registry_free(r); + PASS(); +} + +/* The verdict is the caller's, not the QN's spelling. "latest" contains "test", + * so the scan this replaces read ordinary production code as a test and pushed + * it below a real one; "harness" spells nothing, so the same scan read a test + * as production. Both verdicts here are the extractor's, and both are right. */ +TEST(registry_test_verdict_comes_from_the_caller_not_the_qn) { + cbm_registry_t *r = cbm_registry_new(); + cbm_registry_add(r, "parse", "proj.latest.parse", "Function", false); + cbm_registry_add(r, "parse", "proj.harness.parse", "Function", true); + + cbm_resolution_t res = cbm_registry_resolve(r, "parse", "proj.other", NULL, NULL, 0); + ASSERT_STR_EQ(res.qualified_name, "proj.latest.parse"); + cbm_registry_free(r); + PASS(); +} + +/* A bucket of one is returned whatever its verdict: the penalty orders + * candidates, it does not veto them. */ +TEST(registry_sole_test_candidate_still_resolves) { + cbm_registry_t *r = cbm_registry_new(); + cbm_registry_add(r, "only", "proj.harness.only", "Function", true); + + cbm_resolution_t res = cbm_registry_resolve(r, "only", "proj.other", NULL, NULL, 0); + ASSERT_STR_EQ(res.qualified_name, "proj.harness.only"); + cbm_registry_free(r); + PASS(); +} + TEST(registry_label_of) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Foo", "proj.pkg.Foo", "Class"); - cbm_registry_add(r, "bar", "proj.pkg.bar", "Function"); + cbm_registry_add(r, "Foo", "proj.pkg.Foo", "Class", false); + cbm_registry_add(r, "bar", "proj.pkg.bar", "Function", false); ASSERT_STR_EQ(cbm_registry_label_of(r, "proj.pkg.Foo"), "Class"); ASSERT_STR_EQ(cbm_registry_label_of(r, "proj.pkg.bar"), "Function"); @@ -214,9 +256,9 @@ TEST(registry_label_of) { TEST(registry_find_by_name) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); - cbm_registry_add(r, "main", "proj.srv.main", "Function"); - cbm_registry_add(r, "helper", "proj.util.helper", "Function"); + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); + cbm_registry_add(r, "main", "proj.srv.main", "Function", false); + cbm_registry_add(r, "helper", "proj.util.helper", "Function", false); const char **out = NULL; int count = 0; @@ -235,8 +277,8 @@ TEST(registry_find_by_name) { TEST(registry_no_duplicates) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); - cbm_registry_add(r, "main", "proj.cmd.main", "Function"); /* duplicate */ + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); + cbm_registry_add(r, "main", "proj.cmd.main", "Function", false); /* duplicate */ ASSERT_EQ(cbm_registry_size(r), 1); const char **out = NULL; @@ -252,7 +294,7 @@ TEST(registry_no_duplicates) { TEST(resolve_same_module) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "helper", "proj.pkg.service.helper", "Function"); + cbm_registry_add(r, "helper", "proj.pkg.service.helper", "Function", false); /* Call "helper" from the same module → should resolve */ cbm_resolution_t res = cbm_registry_resolve(r, "helper", "proj.pkg.service", NULL, NULL, 0); @@ -270,9 +312,9 @@ TEST(resolve_same_module) { * Foo::Bar::sub()) where the same sub name exists in multiple packages. */ TEST(resolve_qualified_disambiguates_same_name) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "save", "proj.lib.App.Alpha.save", "Function"); - cbm_registry_add(r, "save", "proj.lib.App.Beta.save", "Function"); - cbm_registry_add(r, "save", "proj.lib.App.Gamma.save", "Function"); + cbm_registry_add(r, "save", "proj.lib.App.Alpha.save", "Function", false); + cbm_registry_add(r, "save", "proj.lib.App.Beta.save", "Function", false); + cbm_registry_add(r, "save", "proj.lib.App.Gamma.save", "Function", false); /* Each fully-qualified call routes to its own package. */ cbm_resolution_t a = @@ -313,8 +355,8 @@ TEST(resolve_qualified_disambiguates_same_name) { * pick arbitrarily under the high-confidence qualified_suffix strategy. */ TEST(resolve_qualified_ambiguous_tail_falls_through) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "run", "proj.svcA.Foo.Bar.run", "Function"); - cbm_registry_add(r, "run", "proj.svcB.Foo.Bar.run", "Function"); + cbm_registry_add(r, "run", "proj.svcA.Foo.Bar.run", "Function", false); + cbm_registry_add(r, "run", "proj.svcB.Foo.Bar.run", "Function", false); /* "Foo::Bar::run" tail matches BOTH candidates → not unique → fall through. */ cbm_resolution_t res = @@ -332,7 +374,7 @@ TEST(resolve_qualified_ambiguous_tail_falls_through) { * `add` callee could ever reach it. */ TEST(registry_indexes_by_passed_name_not_qn_tail) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "add", "proj.lib.add#cfg(test)", "Function"); + cbm_registry_add(r, "add", "proj.lib.add#cfg(test)", "Function", false); cbm_resolution_t res = cbm_registry_resolve(r, "add", "proj.lib.caller", NULL, NULL, 0); ASSERT_STR_EQ(res.qualified_name, "proj.lib.add#cfg(test)"); @@ -357,9 +399,9 @@ TEST(registry_indexes_by_passed_name_not_qn_tail) { * lookup a bare module reference needs. */ TEST(registry_indexes_a_dotted_name_under_its_tail_too) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "resource.aws_instance.web", "proj.main.resource.aws_instance.web", - "Class"); - cbm_registry_add(r, "helper.py", "proj.pkg.helper", "Module"); + cbm_registry_add(r, "resource.aws_instance.web", "proj.main.resource.aws_instance.web", "Class", + false); + cbm_registry_add(r, "helper.py", "proj.pkg.helper", "Module", false); cbm_resolution_t tail = cbm_registry_resolve(r, "web", "proj.main", NULL, NULL, 0); ASSERT_STR_EQ(tail.qualified_name, "proj.main.resource.aws_instance.web"); @@ -377,7 +419,7 @@ TEST(registry_indexes_a_dotted_name_under_its_tail_too) { TEST(resolve_import_map) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.pkg.worker.Process", "Function"); + cbm_registry_add(r, "Process", "proj.pkg.worker.Process", "Function", false); /* Import map: "worker" → "proj.pkg.worker" */ const char *keys[] = {"worker"}; @@ -400,9 +442,9 @@ TEST(resolve_import_map) { * file. Regression for the @/lib/auth-style import case. */ TEST(resolve_import_map_bare_function) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "requireAdmin", "proj.lib.authorization.requireAdmin", "Function"); + cbm_registry_add(r, "requireAdmin", "proj.lib.authorization.requireAdmin", "Function", false); /* Same name in another module — without the fix this is what gets picked. */ - cbm_registry_add(r, "requireAdmin", "proj.lib.users.requireAdmin", "Function"); + cbm_registry_add(r, "requireAdmin", "proj.lib.users.requireAdmin", "Function", false); const char *keys[] = {"requireAdmin"}; const char *vals[] = {"proj.lib.authorization"}; @@ -423,7 +465,7 @@ TEST(resolve_import_map_bare_function) { * Regression for #875. */ TEST(resolve_import_map_bare_alias) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "scan_bash", "proj.security_scan.scan_bash", "Function"); + cbm_registry_add(r, "scan_bash", "proj.security_scan.scan_bash", "Function", false); /* Import map: alias "_scan_bash" → FULL SYMBOL QN (not the module). */ const char *keys[] = {"_scan_bash"}; const char *vals[] = {"proj.security_scan.scan_bash"}; @@ -440,10 +482,10 @@ TEST(resolve_import_map_bare_alias) { * already on main via #875/#979; this locks the Yui G1 shape. */ TEST(resolve_import_map_aliased_from_import) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "execute", "proj.services.satori_bridge.gate.execute", "Function"); + cbm_registry_add(r, "execute", "proj.services.satori_bridge.gate.execute", "Function", false); /* Alias ghost must not win if somehow registered. */ cbm_registry_add(r, "bridge_execute", "proj.services.satori_bridge.gate.bridge_execute", - "Function"); + "Function", false); const char *keys[] = {"bridge_execute"}; const char *vals[] = {"proj.services.satori_bridge.gate.execute"}; @@ -459,7 +501,7 @@ TEST(resolve_import_map_aliased_from_import) { TEST(resolve_unique_name) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "UniqueFunc", "proj.deep.path.UniqueFunc", "Function"); + cbm_registry_add(r, "UniqueFunc", "proj.deep.path.UniqueFunc", "Function", false); /* Call "UniqueFunc" — only one candidate project-wide */ cbm_resolution_t res = @@ -473,7 +515,7 @@ TEST(resolve_unique_name) { TEST(resolve_unresolved) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "foo", "proj.pkg.foo", "Function"); + cbm_registry_add(r, "foo", "proj.pkg.foo", "Function", false); /* Call "nonexistent" — not in registry */ cbm_resolution_t res = cbm_registry_resolve(r, "nonexistent", "proj.other", NULL, NULL, 0); @@ -490,7 +532,7 @@ TEST(resolve_many_nodes) { char name[32], qn[64]; snprintf(name, sizeof(name), "func_%d", i); snprintf(qn, sizeof(qn), "proj.pkg.func_%d", i); - cbm_registry_add(r, name, qn, "Function"); + cbm_registry_add(r, name, qn, "Function", false); } ASSERT_EQ(cbm_registry_size(r), 500); @@ -528,8 +570,8 @@ TEST(confidence_band_speculative) { TEST(resolve_suffix_match) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(r, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(r, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(r, "Process", "proj.svcB.Process", "Function", false); /* Caller in svcA — should prefer svcA via import distance */ cbm_resolution_t res = cbm_registry_resolve(r, "Process", "proj.svcA.caller", NULL, NULL, 0); @@ -552,7 +594,7 @@ TEST(resolve_caps_unresolvably_ambiguous_names) { for (int i = 0; i < 300; i++) { char qn[64]; snprintf(qn, sizeof(qn), "proj.mod%d.flags", i); - cbm_registry_add(r, "flags", qn, "Variable"); + cbm_registry_add(r, "flags", qn, "Variable", false); } cbm_resolution_t res = cbm_registry_resolve(r, "flags", "proj.other.caller", NULL, NULL, 0); ASSERT_TRUE(res.qualified_name == NULL || res.qualified_name[0] == '\0'); @@ -570,7 +612,7 @@ TEST(resolve_caps_unresolvably_ambiguous_names) { TEST(resolve_import_map_suffix) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Foo", "proj.other.sub.Foo", "Function"); + cbm_registry_add(r, "Foo", "proj.other.sub.Foo", "Function", false); const char *keys[] = {"other"}; const char *vals[] = {"proj.other"}; @@ -592,7 +634,7 @@ TEST(resolve_is_import_reachable) { /* Test import reachability through unique_name confidence penalty. * is_import_reachable is static, so we test it indirectly. */ cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Helper", "proj.shared.utils.Helper", "Function"); + cbm_registry_add(r, "Helper", "proj.shared.utils.Helper", "Function", false); /* With import covering the module → full confidence */ const char *keys1[] = {"utils"}; @@ -615,7 +657,7 @@ TEST(resolve_is_import_reachable) { TEST(resolve_import_reachable_prefix) { /* "proj.handler.sub.Process" should be reachable via import "proj.handler" */ cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.handler.sub.Process", "Function"); + cbm_registry_add(r, "Process", "proj.handler.sub.Process", "Function", false); const char *keys[] = {"handler"}; const char *vals[] = {"proj.handler"}; @@ -669,8 +711,8 @@ TEST(reach_cache_memo_matches_uncached_across_files) { TEST(negative_import_rejects_unimported) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.billing.Process", "Function"); - cbm_registry_add(r, "Process", "proj.handler.Process", "Function"); + cbm_registry_add(r, "Process", "proj.billing.Process", "Function", false); + cbm_registry_add(r, "Process", "proj.handler.Process", "Function", false); /* Import only handler's module — suffix_match should prefer handler */ const char *keys[] = {"handler"}; @@ -686,8 +728,8 @@ TEST(negative_import_rejects_unimported) { TEST(fuzzy_resolve_single_candidate) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); - cbm_registry_add(r, "ValidateOrder", "svcB.validators.ValidateOrder", "Function"); + cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); + cbm_registry_add(r, "ValidateOrder", "svcB.validators.ValidateOrder", "Function", false); /* FuzzyResolve should find by simple name even with unknown prefix */ cbm_fuzzy_result_t fr = @@ -701,7 +743,7 @@ TEST(fuzzy_resolve_single_candidate) { TEST(fuzzy_resolve_nonexistent) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function"); + cbm_registry_add(r, "CreateOrder", "svcA.handlers.CreateOrder", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(r, "NonExistent", "svcC.caller", NULL, NULL, 0); @@ -713,8 +755,8 @@ TEST(fuzzy_resolve_nonexistent) { TEST(fuzzy_resolve_multiple_best_by_distance) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "svcA.handlers.Process", "Function"); - cbm_registry_add(r, "Process", "svcB.handlers.Process", "Function"); + cbm_registry_add(r, "Process", "svcA.handlers.Process", "Function", false); + cbm_registry_add(r, "Process", "svcB.handlers.Process", "Function", false); /* Caller in svcA — should prefer svcA */ cbm_fuzzy_result_t fr = @@ -733,7 +775,7 @@ TEST(fuzzy_resolve_multiple_best_by_distance) { TEST(fuzzy_resolve_deep_name_extraction) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "DoWork", "myproject.utils.DoWork", "Function"); + cbm_registry_add(r, "DoWork", "myproject.utils.DoWork", "Function", false); /* Deeply qualified callee — should extract "DoWork" */ cbm_fuzzy_result_t fr = @@ -758,7 +800,7 @@ TEST(fuzzy_resolve_empty_registry) { TEST(fuzzy_resolve_confidence_single) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Handler", "proj.svc.Handler", "Function"); + cbm_registry_add(r, "Handler", "proj.svc.Handler", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(r, "unknownPkg.Handler", "proj.caller", NULL, NULL, 0); @@ -772,8 +814,8 @@ TEST(fuzzy_resolve_confidence_single) { TEST(fuzzy_resolve_confidence_distance) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Process", "proj.svcA.Process", "Function"); - cbm_registry_add(r, "Process", "proj.svcB.Process", "Function"); + cbm_registry_add(r, "Process", "proj.svcA.Process", "Function", false); + cbm_registry_add(r, "Process", "proj.svcB.Process", "Function", false); cbm_fuzzy_result_t fr = cbm_registry_fuzzy_resolve(r, "unknownPkg.Process", "proj.svcA.other", NULL, NULL, 0); @@ -787,7 +829,7 @@ TEST(fuzzy_resolve_confidence_distance) { TEST(fuzzy_penalty_unreachable_import) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function", false); /* Import for different module → confidence halved */ const char *keys[] = {"other"}; @@ -804,7 +846,7 @@ TEST(fuzzy_penalty_unreachable_import) { TEST(fuzzy_no_import_map_passthrough) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function"); + cbm_registry_add(r, "Handler", "proj.billing.Handler", "Function", false); /* nil import map → full confidence */ cbm_fuzzy_result_t fr = @@ -913,8 +955,8 @@ TEST(registry_tie_break_is_independent_of_registration_order) { cbm_registry_t *forward = cbm_registry_new(); cbm_registry_t *backward = cbm_registry_new(); for (int i = 0; i < n; i++) { - cbm_registry_add(forward, "dev_name", cands[i], labels[i]); - cbm_registry_add(backward, "dev_name", cands[n - 1 - i], labels[n - 1 - i]); + cbm_registry_add(forward, "dev_name", cands[i], labels[i], false); + cbm_registry_add(backward, "dev_name", cands[n - 1 - i], labels[n - 1 - i], false); } /* A far-away caller with no imports: every candidate scores the same. */ @@ -932,10 +974,10 @@ TEST(registry_tie_break_is_independent_of_registration_order) { /* Equal depth: the smaller QN, from either order. */ cbm_registry_t *lex_a = cbm_registry_new(); cbm_registry_t *lex_b = cbm_registry_new(); - cbm_registry_add(lex_a, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function"); - cbm_registry_add(lex_a, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function"); - cbm_registry_add(lex_b, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function"); - cbm_registry_add(lex_b, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function"); + cbm_registry_add(lex_a, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function", false); + cbm_registry_add(lex_a, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function", false); + cbm_registry_add(lex_b, "sg_next", "proj.include.linux.scatterlist.sg_next", "Function", false); + cbm_registry_add(lex_b, "sg_next", "proj.tools.virtio.scatterlist.sg_next", "Function", false); cbm_resolution_t la = cbm_registry_resolve(lex_a, "sg_next", "proj.drivers.scsi.arm_scsi", NULL, NULL, 0); cbm_resolution_t lb = @@ -945,9 +987,9 @@ TEST(registry_tie_break_is_independent_of_registration_order) { /* Proximity still outranks depth: the sibling wins over a shallower stranger. */ cbm_registry_t *near = cbm_registry_new(); - cbm_registry_add(near, "vnic_rq_free", "proj.lib.vnic_rq_free", "Function"); + cbm_registry_add(near, "vnic_rq_free", "proj.lib.vnic_rq_free", "Function", false); cbm_registry_add(near, "vnic_rq_free", "proj.drivers.scsi.fnic.vnic_rq.vnic_rq_free", - "Function"); + "Function", false); cbm_resolution_t nr = cbm_registry_resolve(near, "vnic_rq_free", "proj.drivers.scsi.fnic.fnic_res", NULL, NULL, 0); ASSERT_STR_EQ(nr.qualified_name, "proj.drivers.scsi.fnic.vnic_rq.vnic_rq_free"); @@ -1209,8 +1251,8 @@ TEST(weak_call_guards_share_one_drop_list) { * Signal.send). Regression guard for #1000. */ TEST(resolve_import_map_alias_with_suffix_hits_method) { cbm_registry_t *r = cbm_registry_new(); - cbm_registry_add(r, "user_logged_in", "proj.auth.signals.user_logged_in", "Variable"); - cbm_registry_add(r, "send", "proj.auth.signals.user_logged_in.send", "Method"); + cbm_registry_add(r, "user_logged_in", "proj.auth.signals.user_logged_in", "Variable", false); + cbm_registry_add(r, "send", "proj.auth.signals.user_logged_in.send", "Method", false); const char *keys[] = {"user_logged_in"}; const char *vals[] = {"proj.auth.signals.user_logged_in"}; cbm_resolution_t res = @@ -1244,6 +1286,9 @@ SUITE(registry) { RUN_TEST(registry_create_free); RUN_TEST(registry_free_null); RUN_TEST(registry_add_and_exists); + RUN_TEST(registry_prefers_non_test_over_test_candidate); + RUN_TEST(registry_test_verdict_comes_from_the_caller_not_the_qn); + RUN_TEST(registry_sole_test_candidate_still_resolves); RUN_TEST(registry_label_of); RUN_TEST(registry_find_by_name); RUN_TEST(registry_no_duplicates);