test(registry): pin the test-double rule and the verdict through full and incremental

- registry_test_double_path_table: the anchored predicate's positives
  (fake, mock, mocks, testing, testutil directory segments; mock_*.go,
  fake_*.go) and negatives (inspect, latest, contest, fakeroot, mockery,
  testingutil, mocking.go, mock_store.py, a file named fake, a different
  case, NULL).
- registry_swift_overload_choice_uses_stored_verdicts: the Swift overload
  choosers score with the verdicts stored at registration; without them the
  smaller QN, a test double, wins the tie.
- pipeline_registry_ranks_by_extractor_verdict_full_and_incremental: the
  `latest` vs `_test.go` misranking the QN scan made, through a full index,
  a closure-repair incremental run and a forced legacy-partial run.
- pipeline_registry_ranks_test_doubles_last_full_and_incremental: a
  generated fake that out-ranks the real package on proximity, through the
  same three runs.

Each fails with the piece it pins reverted (predicate, seeder verdict,
incremental seeder rule, closure-repair verdict carry, Swift verdict carry).

Co-authored-by: Chad <4307099+BobbieBarker@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
Martin Vogel
2026-11-11 20:51:42 +01:00
co-authored by Chad
parent 7e9186cf8a
commit cf4f117db4
2 changed files with 192 additions and 0 deletions
+128
View File
@@ -6619,6 +6619,132 @@ TEST(pipeline_incremental_cross_file_call_reference_matches_fresh_full) {
PASS();
}
/* #2427 rank probes: one bare call, two same-named candidates, and the
* registry's test ranking alone decides which one the call binds. Three runs
* on one database: a full index, then a comment-only edit of the caller on
* the closure-repair route, then another on the forced legacy-partial route.
* Both incremental routes re-resolve the caller against a registry seeded from
* stored nodes rather than the extractor's definitions, which is where a
* seeder that loses the verdict shows. */
typedef struct {
int want[3];
int other[3];
cbm_incremental_route_t route[3];
} rank_probe_t;
static int rank_probe_count(const char *db, const char *project, const char *source,
const char *target, const char *file) {
cbm_store_t *s = cbm_store_open_path(db);
if (!s) {
return -1;
}
int n = named_edge_to_file_count(s, project, "CALLS", source, target, file);
cbm_store_close(s);
return n;
}
static void rank_probe(const char *tmp, const char *caller_path, const char *caller_src,
const char *source, const char *target, const char *want_file,
const char *other_file, rank_probe_t *out) {
char db[512];
snprintf(db, sizeof(db), "%s/rank.db", tmp);
for (int run = 0; run < 3; run++) {
out->want[run] = -1;
out->other[run] = -1;
out->route[run] = CBM_INCREMENTAL_ROUTE_NONE;
if (run > 0) {
char edited[512];
snprintf(edited, sizeof(edited), "%s// edit %d\n", caller_src, run);
write_temp_file(tmp, caller_path, edited);
}
cbm_pipeline_incremental_test_reset_faults();
#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API
if (run == 2) {
cbm_pipeline_incremental_test_force_legacy_partial_once();
}
#endif
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db, CBM_MODE_FULL);
if (!p) {
return;
}
int rc = cbm_pipeline_run(p);
out->route[run] = cbm_pipeline_incremental_test_last_route();
char project[256];
snprintf(project, sizeof(project), "%s", cbm_pipeline_project_name(p));
cbm_pipeline_free(p);
if (rc != 0) {
return;
}
out->want[run] = rank_probe_count(db, project, source, target, want_file);
out->other[run] = rank_probe_count(db, project, source, target, other_file);
}
}
/* "latest" spells "test", so the QN scan the extractor's verdict replaced
* ranked the production Parse below a helper in a _test.go file, whose Go QN
* names only its package and so spells nothing. The verdict ranks them the
* right way round, and both incremental routes must read the same verdict
* back from the stored nodes: the closure-repair proxies once carried no
* is_test, every candidate scored as production, and the smaller QN -- the
* test helper -- won. */
TEST(pipeline_registry_ranks_by_extractor_verdict_full_and_incremental) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_rank_verdict_XXXXXX");
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
static const char caller[] = "package app\n\nfunc Run() int { return Parse() }\n";
write_temp_file(tmp, "go.mod", "module example.com/rank\n\ngo 1.22\n");
write_temp_file(tmp, "latest/parse.go", "package latest\n\nfunc Parse() int { return 1 }\n");
write_temp_file(tmp, "harness/parse_test.go",
"package harness\n\nfunc Parse() int { return 2 }\n");
write_temp_file(tmp, "app/run.go", caller);
rank_probe_t r;
rank_probe(tmp, "app/run.go", caller, "Run", "Parse", "latest/parse.go",
"harness/parse_test.go", &r);
th_rmtree(tmp);
ASSERT_EQ(r.want[0], 1); /* full */
ASSERT_EQ(r.other[0], 0);
ASSERT_EQ(r.route[1], CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR);
ASSERT_EQ(r.want[1], 1);
ASSERT_EQ(r.other[1], 0);
#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API
ASSERT_EQ(r.route[2], CBM_INCREMENTAL_ROUTE_LEGACY_PARTIAL);
#endif
ASSERT_EQ(r.want[2], 1);
ASSERT_EQ(r.other[2], 0);
PASS();
}
/* A generated fake beside its callers: client/fake shares more of the caller's
* package path than the real store does, so on the extractor's verdict alone
* (a fake is not a test file) the fake wins on proximity. The test-double path
* rule ranks it last in the full index and, through the incremental seed's own
* use of the rule, on both incremental routes. */
TEST(pipeline_registry_ranks_test_doubles_last_full_and_incremental) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_rank_double_XXXXXX");
ASSERT_NOT_NULL(cbm_mkdtemp(tmp));
static const char caller[] = "package client\n\nfunc Run() int { return Load() }\n";
write_temp_file(tmp, "go.mod", "module example.com/rank\n\ngo 1.22\n");
write_temp_file(tmp, "store/load.go", "package store\n\nfunc Load() int { return 1 }\n");
write_temp_file(tmp, "client/fake/load.go", "package fake\n\nfunc Load() int { return 2 }\n");
write_temp_file(tmp, "client/run.go", caller);
rank_probe_t r;
rank_probe(tmp, "client/run.go", caller, "Run", "Load", "store/load.go", "client/fake/load.go",
&r);
th_rmtree(tmp);
ASSERT_EQ(r.want[0], 1); /* full */
ASSERT_EQ(r.other[0], 0);
ASSERT_EQ(r.route[1], CBM_INCREMENTAL_ROUTE_CLOSURE_REPAIR);
ASSERT_EQ(r.want[1], 1);
ASSERT_EQ(r.other[1], 0);
#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API
ASSERT_EQ(r.route[2], CBM_INCREMENTAL_ROUTE_LEGACY_PARTIAL);
#endif
ASSERT_EQ(r.want[2], 1);
ASSERT_EQ(r.other[2], 0);
PASS();
}
/* A semantic control file can change an unchanged source file's exact target.
* Both target modules deliberately export the same short name: without the
* tsconfig path mapping, project-wide unique-name fallback is ambiguous and
@@ -34486,6 +34612,8 @@ SUITE(pipeline) {
RUN_TEST(pipeline_call_reference_sequential_parallel_edge_set_parity);
RUN_TEST(pipeline_complexity_props_independent_of_worker_order);
RUN_TEST(pipeline_incremental_cross_file_call_reference_matches_fresh_full);
RUN_TEST(pipeline_registry_ranks_by_extractor_verdict_full_and_incremental);
RUN_TEST(pipeline_registry_ranks_test_doubles_last_full_and_incremental);
RUN_TEST(pipeline_incremental_changed_target_invalidates_stale_inbound_call_reference);
RUN_TEST(pipeline_incremental_parallel_registry_nodes_advance_shared_ids);
#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API
+64
View File
@@ -243,6 +243,68 @@ TEST(registry_sole_test_candidate_still_resolves) {
PASS();
}
/* The test-double path rule: whole directory segments named fake, mock, mocks,
* testing or testutil, and Go files named mock_*.go or fake_*.go. Anchored, so
* the substrings the old QN scan tripped on (inspect, latest, contest) and
* near-miss names (fakeroot, mockery, testingutil) stay ordinary code. */
TEST(registry_test_double_path_table) {
static const char *const doubles[] = {
"pkg/client/fake/clientset.go",
"fake/x.go",
"internal/mocks/store.go",
"pkg/mock/store.go",
"pkg/testing/fixtures.go",
"internal/testutil/helpers.go",
"pkg/mock_store.go",
"pkg/fake_clock.go",
"mock_store.go",
"src/fake/deep/nested/util.py",
};
static const char *const ordinary[] = {
"pkg/inspect/x.go", "pkg/latest/x.go", "pkg/contest/x.go", "pkg/fakeroot/x.go",
"pkg/mockery/x.go", "pkg/testingutil/x.go", "pkg/mocking.go", "pkg/mock_store.py",
"pkg/fake_clock.golang", "pkg/fake.go", "pkg/x/fake", "pkg/Fake/x.go",
"pkg/myfake/x.go", "pkg/fake-client/x.go", "testing.go", "",
};
char why[256];
for (size_t i = 0; i < sizeof(doubles) / sizeof(doubles[0]); i++) {
if (!cbm_registry_test_double_path(doubles[i])) {
snprintf(why, sizeof(why), "expected a test-double path: %s", doubles[i]);
FAIL(why);
}
}
for (size_t i = 0; i < sizeof(ordinary) / sizeof(ordinary[0]); i++) {
if (cbm_registry_test_double_path(ordinary[i])) {
snprintf(why, sizeof(why), "expected an ordinary path: %s", ordinary[i]);
FAIL(why);
}
}
ASSERT_FALSE(cbm_registry_test_double_path(NULL));
PASS();
}
/* The Swift overload choosers score with the verdicts stored at registration,
* carried through their own filtered lists. Both overloads fit the call and tie
* on proximity; without the verdicts the smaller QN (the test double) would win
* the tie. */
TEST(registry_swift_overload_choice_uses_stored_verdicts) {
cbm_registry_t *r = cbm_registry_new();
const char *double_qn = "proj.Fixtures.Store.load(path:String)";
const char *real_qn = "proj.Zeta.Store.load(path:String)";
cbm_registry_add(r, "load", double_qn, "Method", true);
cbm_registry_add(r, "load", real_qn, "Method", false);
cbm_registry_set_swift_signature(r, double_qn, 0, 1, NULL);
cbm_registry_set_swift_signature(r, real_qn, 0, 1, NULL);
CBMCallArg arg = {.keyword = "path"};
CBMCall call = {.callee_name = "load", .args = &arg, .arg_count = 1};
const char *out[4] = {0};
ASSERT_EQ(cbm_registry_swift_candidates(r, &call, "proj.Caller", NULL, 0, out, 4), 1);
ASSERT_STR_EQ(out[0], real_qn);
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", false);
@@ -3382,6 +3444,8 @@ SUITE(registry) {
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_test_double_path_table);
RUN_TEST(registry_swift_overload_choice_uses_stored_verdicts);
RUN_TEST(registry_label_of);
RUN_TEST(registry_value_target_field_twin);
RUN_TEST(registry_find_by_name);