mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Since #2133 a cross-repo result owns heap memory: a wildcard run records each store it skips in result.skipped_projects, and the caller releases it with cbm_cross_repo_result_free(). Fifteen tests in test_cross_repo.c received a result and never released it. The one that seeds a pre-#768 store next to a wildcard target records a skip, so it leaked the skip array and the skipped project name: 32 bytes in 2 allocations, allocated in cr_record_skip. Every assertion still passed. The macOS leak lane (test-lsan-macos) was the run that reported it, because it builds with Homebrew LLVM and sets detect_leaks=1. The ordinary macOS runs, the local ones included, are built with Apple clang, where leak detection is off and cannot be turned on, so they stayed green. Each test now releases its result right after the fixture is torn down and before the first assertion, which is the shape the two tests that already released theirs use. The release therefore also runs when an assertion fails, and the scalar fields the assertions read stay valid after it. The repro_issue523 cases release their results the same way. They name their targets, so they record no skip and did not leak, but they hold a result and follow the same ownership rule. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
493 lines
22 KiB
C
493 lines
22 KiB
C
/*
|
|
* repro_issue523.c — Reproduce-first case for issue #523.
|
|
*
|
|
* BUG: "cross-repo-intelligence returns 0 edges for a byte-identical call/route"
|
|
*
|
|
* Root cause (pass_calls.c::resolve_single_call):
|
|
*
|
|
* When a Python client uses `import requests` and calls
|
|
* `requests.get("/api/orders/{id}")`, the `requests` package is an external
|
|
* pip dependency whose source is NOT present in the indexed tree.
|
|
* `cbm_registry_resolve` resolves the callee name to a candidate QN
|
|
* containing "requests", but `cbm_gbuf_find_by_qn(ctx->gbuf, res.qualified_name)`
|
|
* returns NULL — the node does not exist in the graph because `requests` was
|
|
* never indexed. The guard at pass_calls.c::resolve_single_call line ~406:
|
|
*
|
|
* const cbm_gbuf_node_t *target_node = cbm_gbuf_find_by_qn(ctx->gbuf, res.qualified_name);
|
|
* if (!target_node || source_node->id == target_node->id)
|
|
* return 0; ← call is SILENTLY DROPPED
|
|
*
|
|
* causes the call to be silently dropped before it ever reaches
|
|
* `emit_classified_edge` / `emit_http_async_edge`. No HTTP_CALLS edge is
|
|
* created in the client project DB.
|
|
*
|
|
* Without an HTTP_CALLS edge in the client DB, `match_http_routes` in
|
|
* pass_cross_repo.c finds nothing to iterate over, and `cbm_cross_repo_match`
|
|
* returns http_edges == 0 — even when the server project has a perfectly
|
|
* matching Route node (byte-identical path, correct method) and a HANDLES
|
|
* edge pointing to the handler function.
|
|
*
|
|
* Expected (correct) behaviour:
|
|
* A call to an external HTTP client library (e.g. `requests.get`) with a
|
|
* URL/path first argument MUST produce an HTTP_CALLS edge in the client
|
|
* project DB, even when the library's source is not indexed. The linker
|
|
* should detect the service-pattern match on the resolved QN substring
|
|
* ("requests") and emit the edge before consulting the node graph.
|
|
* Subsequently, `cbm_cross_repo_match` must produce at least one
|
|
* CROSS_HTTP_CALLS edge linking the client caller to the server route handler
|
|
* when the client url_path (canonicalized) matches the server Route QN.
|
|
*
|
|
* Actual (buggy) behaviour:
|
|
* cbm_cross_repo_match returns http_edges == 0. The assertion below is RED.
|
|
*
|
|
* Companion: pass_calls.c (sequential path) and pass_parallel.c (parallel path)
|
|
* both share the same guard; fixing one requires fixing both.
|
|
*
|
|
* Note on parallel pipeline:
|
|
* HTTP_CALLS edges are produced on BOTH the sequential (< 50 files) and
|
|
* parallel (>= 50 files) pipeline paths, so this test uses a small fixture
|
|
* (< 50 files) and exercises the sequential path. The parallel path has the
|
|
* same root cause and is covered by the same fix (pass_parallel.c::
|
|
* finalize_and_emit has an identical unindexed-node guard).
|
|
*/
|
|
|
|
#include "test_framework.h"
|
|
#include "repro_harness.h"
|
|
#include "pipeline/pass_cross_repo.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
/* ── Fixture files ───────────────────────────────────────────────────────── */
|
|
|
|
/*
|
|
* CLIENT SERVICE (order-client):
|
|
* Uses the real `requests` library imported at the top of the file.
|
|
* The `requests` package is NOT present in the indexed tree (no vendored
|
|
* source, no stub) — this is exactly the real-world multi-service scenario.
|
|
* The caller function `fetch_order` makes a GET request to the byte-identical
|
|
* path "/api/orders/{id}" that the server registers.
|
|
*
|
|
* WHY this triggers the bug:
|
|
* cbm_registry_resolve("requests.get", …) returns a candidate QN that
|
|
* contains "requests" (service-pattern match → CBM_SVC_HTTP), BUT
|
|
* cbm_gbuf_find_by_qn returns NULL for that QN because no `requests` node
|
|
* was ever inserted into the graph buffer. resolve_single_call returns 0,
|
|
* the call is dropped, and no HTTP_CALLS edge is created.
|
|
*/
|
|
static const RFile client_files[] = {
|
|
{"client/orders.py",
|
|
"import requests\n"
|
|
"\n"
|
|
"\n"
|
|
"BASE_URL = \"http://order-service:8080\"\n"
|
|
"\n"
|
|
"\n"
|
|
"def fetch_order(order_id):\n"
|
|
" \"\"\"Fetch a single order from the order service.\"\"\"\n"
|
|
" return requests.get(\"/api/orders/{id}\", params={\"id\": order_id})\n"
|
|
"\n"
|
|
"\n"
|
|
"def list_orders():\n"
|
|
" \"\"\"Fetch all orders from the order service.\"\"\"\n"
|
|
" return requests.get(\"/api/orders\")\n"},
|
|
};
|
|
enum { N_CLIENT_FILES = (int)(sizeof(client_files) / sizeof(client_files[0])) };
|
|
|
|
/*
|
|
* SERVER SERVICE (order-service):
|
|
* A minimal Flask application that defines the route handler for the path
|
|
* the client calls. The path "/api/orders/{id}" is byte-identical to the
|
|
* client's call argument. Flask uses `{id}` parameter syntax; the extractor
|
|
* mints a Route node with QN `__route__GET__/api/orders/{}` (canonicalized
|
|
* via cbm_route_canon_path). A HANDLES edge links the Route to `get_order`.
|
|
*/
|
|
static const RFile server_files[] = {
|
|
{"server/app.py", "from flask import Flask, jsonify\n"
|
|
"\n"
|
|
"app = Flask(__name__)\n"
|
|
"\n"
|
|
"\n"
|
|
"@app.get(\"/api/orders/{id}\")\n"
|
|
"def get_order(order_id):\n"
|
|
" \"\"\"Return a single order by id.\"\"\"\n"
|
|
" return jsonify({\"id\": order_id, \"status\": \"ok\"})\n"
|
|
"\n"
|
|
"\n"
|
|
"@app.get(\"/api/orders\")\n"
|
|
"def list_orders():\n"
|
|
" \"\"\"Return all orders.\"\"\"\n"
|
|
" return jsonify({\"orders\": []})\n"},
|
|
};
|
|
enum { N_SERVER_FILES = (int)(sizeof(server_files) / sizeof(server_files[0])) };
|
|
|
|
/* ── Reproduction test ───────────────────────────────────────────────────── */
|
|
|
|
/*
|
|
* TEST: repro_issue523_crossrepo_http_calls_edge
|
|
*
|
|
* Steps:
|
|
* 1. Index the CLIENT service — expect HTTP_CALLS >= 1 (currently 0: RED
|
|
* because unindexed `requests` causes the call to be dropped).
|
|
* 2. Index the SERVER service — expect Route nodes >= 1 (this side is GREEN;
|
|
* Flask decorator extraction is correct).
|
|
* 3. Run cbm_cross_repo_match(client_project, [server_project], 1).
|
|
* 4. Assert result.http_edges >= 1 — this is the cross-repo edge count.
|
|
* Currently 0 because step 1 yields no HTTP_CALLS to match.
|
|
*
|
|
* The assertion at step 4 is the canonical RED line. Steps 1 and 3 are
|
|
* diagnostic: step 1 prints the http_calls count so the fix can be verified
|
|
* independently; step 3 fails fast if the server was not indexed correctly.
|
|
*/
|
|
TEST(repro_issue523_crossrepo_http_calls_edge) {
|
|
/* ── Index client service ─────────────────────────────────── */
|
|
RProj client;
|
|
cbm_store_t *client_store = rh_index_files(&client, client_files, N_CLIENT_FILES);
|
|
ASSERT_NOT_NULL(client_store);
|
|
|
|
int client_http = rh_count_edges(client_store, client.project, "HTTP_CALLS");
|
|
fprintf(stderr,
|
|
" [523] client HTTP_CALLS=%d "
|
|
"(expected>=1; 0=bug: requests not indexed → call dropped)\n",
|
|
client_http);
|
|
|
|
cbm_store_close(client_store);
|
|
client_store = NULL; /* re-opened inside cbm_cross_repo_match via cache dir */
|
|
|
|
/* ── Index server service ─────────────────────────────────── */
|
|
RProj server;
|
|
cbm_store_t *server_store = rh_index_files(&server, server_files, N_SERVER_FILES);
|
|
ASSERT_NOT_NULL(server_store);
|
|
|
|
int server_routes = rh_count_label(server_store, server.project, "Route");
|
|
fprintf(stderr, " [523] server Route nodes=%d (expected>=2; 0=extractor broken)\n",
|
|
server_routes);
|
|
/* Server-side extraction is correct — if this fails the test environment is
|
|
* broken, not the cross-repo linker. Fail fast with a clear message. */
|
|
if (server_routes < 1) {
|
|
cbm_store_close(server_store);
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, server_store);
|
|
FAIL("server route extraction broken — test environment issue, not issue #523");
|
|
}
|
|
|
|
cbm_store_close(server_store);
|
|
server_store = NULL; /* re-opened bidirectionally inside cbm_cross_repo_match */
|
|
|
|
/* ── Cross-repo match ─────────────────────────────────────── */
|
|
/*
|
|
* cbm_cross_repo_match opens both project DBs from the cache directory
|
|
* (the same $HOME/.cache/codebase-memory-mcp/<project>.db paths that
|
|
* rh_open_indexed wrote). It iterates HTTP_CALLS edges in the client DB
|
|
* and looks for matching Route QNs in the server DB.
|
|
*
|
|
* Correct: http_edges >= 1 (at least one edge for /api/orders/{id}).
|
|
* Buggy: http_edges == 0 (no HTTP_CALLS in client → nothing to match).
|
|
*/
|
|
const char *server_project = server.project;
|
|
cbm_cross_repo_result_t result = cbm_cross_repo_match(client.project, &server_project, 1);
|
|
|
|
fprintf(stderr,
|
|
" [523] cross_repo http_edges=%d "
|
|
"(expected>=1; 0=bug confirmed: issue #523)\n",
|
|
result.http_edges);
|
|
|
|
/* ── Cleanup ──────────────────────────────────────────────── */
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
cbm_cross_repo_result_free(&result);
|
|
|
|
/*
|
|
* WHY RED: result.http_edges == 0 on current code.
|
|
*
|
|
* The root cause is in resolve_single_call (pass_calls.c ~line 405):
|
|
* cbm_gbuf_find_by_qn returns NULL for the `requests` QN (not indexed).
|
|
* The function returns 0 before reaching emit_classified_edge.
|
|
* No HTTP_CALLS edge is written to the client DB.
|
|
* match_http_routes in pass_cross_repo.c finds no HTTP_CALLS to iterate.
|
|
* cbm_cross_repo_match returns http_edges = 0.
|
|
*
|
|
* The fix must allow emit_http_async_edge to fire for service-pattern
|
|
* matches even when the resolved target node is absent from the graph buffer
|
|
* (i.e., skip the cbm_gbuf_find_by_qn guard for CBM_SVC_HTTP / CBM_SVC_ASYNC
|
|
* calls, or create a synthetic stub node so the guard passes).
|
|
*/
|
|
ASSERT_GTE(result.http_edges, 1);
|
|
|
|
PASS();
|
|
}
|
|
|
|
/* ── Cross-repo matcher gaps (distilled from PR #536, same issue #523) ───── */
|
|
/*
|
|
* The pass_calls half of #523 (HTTP_CALLS edges for unindexed client libs) is
|
|
* fixed; these cases cover the remaining pass_cross_repo.c matcher gaps:
|
|
*
|
|
* 1. url_path can be a FULL URL ("http://host:8080/v2/orders") — stored raw
|
|
* from the call's first string arg. cbm_route_canon_path never strips
|
|
* scheme/authority, so the built route QN embeds the host and the exact
|
|
* lookup misses. → cr_url_path()
|
|
* 2. A CONCRETE client path ("/v2/orders/123") never exact-matches a
|
|
* templated route QN ("__route__GET__/v2/orders/{}").
|
|
* → find_route_handler_fuzzy() / cr_path_matches_template()
|
|
* 3. match_http_routes only ran src→tgt: matching initiated from the
|
|
* provider side finds nothing (the provider has no outbound HTTP_CALLS).
|
|
* → reverse-direction run in cbm_cross_repo_match()
|
|
* 4. Worse, a provider-side run DESTROYED existing links: delete_cross_edges
|
|
* wiped the provider's reverse edges and — without reverse matching —
|
|
* nothing recreated them. → the reverse-direction run restores them; row
|
|
* dedup itself needs no guard (the edges table upserts on the
|
|
* (source, target, type) UNIQUE key).
|
|
*/
|
|
|
|
/* Index a one-file client and a one-file server project through the production
|
|
* pipeline. Closes both stores again — cbm_cross_repo_match re-opens the DBs
|
|
* from the cache dir. Returns false when either project failed to index or the
|
|
* PRECONDITIONS (client HTTP_CALLS edge, server Route node) are missing, so a
|
|
* broken fixture fails RED instead of vacuously passing. */
|
|
static bool cr536_setup(RProj *client, const char *client_py, RProj *server,
|
|
const char *server_py) {
|
|
memset(client, 0, sizeof(*client));
|
|
memset(server, 0, sizeof(*server));
|
|
cbm_store_t *cs = rh_index(client, "client/orders.py", client_py);
|
|
if (!cs) {
|
|
return false;
|
|
}
|
|
int client_http = rh_count_edges(cs, client->project, "HTTP_CALLS");
|
|
cbm_store_close(cs);
|
|
|
|
cbm_store_t *ss = rh_index(server, "server/app.py", server_py);
|
|
if (!ss) {
|
|
return false;
|
|
}
|
|
int server_routes = rh_count_label(ss, server->project, "Route");
|
|
cbm_store_close(ss);
|
|
|
|
fprintf(stderr, " [523] precondition: client HTTP_CALLS=%d server Routes=%d (both >=1)\n",
|
|
client_http, server_routes);
|
|
return client_http >= 1 && server_routes >= 1;
|
|
}
|
|
|
|
/* Count CROSS_HTTP_CALLS rows in a project DB (re-opened from the cache dir,
|
|
* because cbm_cross_repo_match wrote through its own store handles). */
|
|
static int cr536_count_cross(RProj *proj) {
|
|
cbm_store_t *st = cbm_store_open_path(proj->dbpath);
|
|
if (!st) {
|
|
return -1;
|
|
}
|
|
int n = rh_count_edges(st, proj->project, "CROSS_HTTP_CALLS");
|
|
cbm_store_close(st);
|
|
return n;
|
|
}
|
|
|
|
/*
|
|
* TEST: url_path stored as a full URL must match a bare-path route.
|
|
* WHY RED on unfixed code: the route QN is built from the raw url_path, so the
|
|
* lookup key is "__route__GET__http://order-api.internal:8080/v2/orders" and
|
|
* never matches the server's "__route__GET__/v2/orders".
|
|
*/
|
|
TEST(repro_issue523_scheme_stripped_url_match) {
|
|
RProj client, server;
|
|
bool ok = cr536_setup(&client,
|
|
"import requests\n"
|
|
"\n"
|
|
"\n"
|
|
"def fetch_orders():\n"
|
|
" \"\"\"Fetch all orders via the service base URL.\"\"\"\n"
|
|
" return requests.get(\"http://order-api.internal:8080/v2/orders\")\n",
|
|
&server,
|
|
"from flask import Flask, jsonify\n"
|
|
"\n"
|
|
"app = Flask(__name__)\n"
|
|
"\n"
|
|
"\n"
|
|
"@app.get(\"/v2/orders\")\n"
|
|
"def list_orders():\n"
|
|
" \"\"\"Return all orders.\"\"\"\n"
|
|
" return jsonify({\"orders\": []})\n");
|
|
if (!ok) {
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
FAIL("fixture precondition failed (client HTTP_CALLS / server Route missing)");
|
|
}
|
|
|
|
const char *tgt = server.project;
|
|
cbm_cross_repo_result_t r = cbm_cross_repo_match(client.project, &tgt, 1);
|
|
fprintf(stderr, " [523] full-URL url_path: http_edges=%d (expected>=1)\n", r.http_edges);
|
|
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
cbm_cross_repo_result_free(&r);
|
|
ASSERT_GTE(r.http_edges, 1);
|
|
PASS();
|
|
}
|
|
|
|
/*
|
|
* TEST: a concrete client path must fuzzy-match a templated server route.
|
|
* WHY RED on unfixed code: "__route__GET__/v2/orders/123" exact-lookup misses
|
|
* "__route__GET__/v2/orders/{}" and there is no template fallback.
|
|
*/
|
|
TEST(repro_issue523_template_fuzzy_match) {
|
|
RProj client, server;
|
|
bool ok = cr536_setup(&client,
|
|
"import requests\n"
|
|
"\n"
|
|
"\n"
|
|
"def fetch_order():\n"
|
|
" \"\"\"Fetch one concrete order.\"\"\"\n"
|
|
" return requests.get(\"/v2/orders/123\")\n",
|
|
&server,
|
|
"from flask import Flask, jsonify\n"
|
|
"\n"
|
|
"app = Flask(__name__)\n"
|
|
"\n"
|
|
"\n"
|
|
"@app.get(\"/v2/orders/{order_id}\")\n"
|
|
"def get_order(order_id):\n"
|
|
" \"\"\"Return one order by id.\"\"\"\n"
|
|
" return jsonify({\"id\": order_id})\n");
|
|
if (!ok) {
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
FAIL("fixture precondition failed (client HTTP_CALLS / server Route missing)");
|
|
}
|
|
|
|
const char *tgt = server.project;
|
|
cbm_cross_repo_result_t r = cbm_cross_repo_match(client.project, &tgt, 1);
|
|
fprintf(stderr, " [523] concrete-vs-template: http_edges=%d (expected>=1)\n", r.http_edges);
|
|
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
cbm_cross_repo_result_free(&r);
|
|
ASSERT_GTE(r.http_edges, 1);
|
|
PASS();
|
|
}
|
|
|
|
/*
|
|
* TEST: matching initiated from the PROVIDER side must still find the link.
|
|
* WHY RED on unfixed code: match_http_routes only scans the source project's
|
|
* HTTP_CALLS — the server has none, so the provider-side run reports 0.
|
|
*/
|
|
TEST(repro_issue523_reverse_direction_match) {
|
|
RProj client, server;
|
|
bool ok = cr536_setup(&client,
|
|
"import requests\n"
|
|
"\n"
|
|
"\n"
|
|
"def fetch_status():\n"
|
|
" \"\"\"Poll the status endpoint.\"\"\"\n"
|
|
" return requests.get(\"/v2/status\")\n",
|
|
&server,
|
|
"from flask import Flask, jsonify\n"
|
|
"\n"
|
|
"app = Flask(__name__)\n"
|
|
"\n"
|
|
"\n"
|
|
"@app.get(\"/v2/status\")\n"
|
|
"def get_status():\n"
|
|
" \"\"\"Return service status.\"\"\"\n"
|
|
" return jsonify({\"status\": \"ok\"})\n");
|
|
if (!ok) {
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
FAIL("fixture precondition failed (client HTTP_CALLS / server Route missing)");
|
|
}
|
|
|
|
/* Provider indexed the match run: server is SRC, client is TGT. */
|
|
const char *tgt = client.project;
|
|
cbm_cross_repo_result_t r = cbm_cross_repo_match(server.project, &tgt, 1);
|
|
int client_cross = cr536_count_cross(&client);
|
|
fprintf(stderr, " [523] provider-side run: http_edges=%d client CROSS_HTTP_CALLS=%d\n",
|
|
r.http_edges, client_cross);
|
|
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
cbm_cross_repo_result_free(&r);
|
|
ASSERT_GTE(r.http_edges, 1);
|
|
ASSERT_GTE(client_cross, 1);
|
|
PASS();
|
|
}
|
|
|
|
/*
|
|
* TEST: the matcher must CONVERGE — running it from either side, repeatedly,
|
|
* leaves exactly one CROSS_HTTP_CALLS row in each DB (no losses, no dupes).
|
|
* WHY RED on unfixed code: run 2 (provider side) starts with
|
|
* delete_cross_edges(server), wiping the reverse edge run 1 wrote into the
|
|
* server DB — and, lacking reverse-direction matching, recreates nothing: a
|
|
* provider-side run permanently DESTROYS the recorded link (server count 0).
|
|
* Duplicate rows are impossible either way (the edges table upserts on the
|
|
* (source, target, type) UNIQUE key); the run-3 assertions pin that too.
|
|
*/
|
|
TEST(repro_issue523_idempotent_cross_edges) {
|
|
RProj client, server;
|
|
bool ok = cr536_setup(&client,
|
|
"import requests\n"
|
|
"\n"
|
|
"\n"
|
|
"def fetch_health():\n"
|
|
" \"\"\"Poll the health endpoint.\"\"\"\n"
|
|
" return requests.get(\"/v2/health\")\n",
|
|
&server,
|
|
"from flask import Flask, jsonify\n"
|
|
"\n"
|
|
"app = Flask(__name__)\n"
|
|
"\n"
|
|
"\n"
|
|
"@app.get(\"/v2/health\")\n"
|
|
"def get_health():\n"
|
|
" \"\"\"Return health.\"\"\"\n"
|
|
" return jsonify({\"health\": \"ok\"})\n");
|
|
if (!ok) {
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
FAIL("fixture precondition failed (client HTTP_CALLS / server Route missing)");
|
|
}
|
|
|
|
/* Run 1: consumer side — creates fwd row (client DB) + rev row (server DB). */
|
|
const char *tgt_srv = server.project;
|
|
cbm_cross_repo_result_t r1 = cbm_cross_repo_match(client.project, &tgt_srv, 1);
|
|
int client_1 = cr536_count_cross(&client);
|
|
int server_1 = cr536_count_cross(&server);
|
|
|
|
/* Run 2: provider side — must re-find the same pair, not destroy it. */
|
|
const char *tgt_cli = client.project;
|
|
cbm_cross_repo_result_t r2 = cbm_cross_repo_match(server.project, &tgt_cli, 1);
|
|
int client_2 = cr536_count_cross(&client);
|
|
int server_2 = cr536_count_cross(&server);
|
|
|
|
/* Run 3: consumer side again — convergence, still exactly one row each. */
|
|
cbm_cross_repo_result_t r3 = cbm_cross_repo_match(client.project, &tgt_srv, 1);
|
|
int client_3 = cr536_count_cross(&client);
|
|
int server_3 = cr536_count_cross(&server);
|
|
|
|
fprintf(stderr,
|
|
" [523] convergence: run1 http=%d (client=%d server=%d) "
|
|
"run2 http=%d (client=%d server=%d) run3 http=%d (client=%d server=%d) "
|
|
"— every count must be 1\n",
|
|
r1.http_edges, client_1, server_1, r2.http_edges, client_2, server_2, r3.http_edges,
|
|
client_3, server_3);
|
|
|
|
rh_cleanup(&client, NULL);
|
|
rh_cleanup(&server, NULL);
|
|
cbm_cross_repo_result_free(&r1);
|
|
cbm_cross_repo_result_free(&r2);
|
|
cbm_cross_repo_result_free(&r3);
|
|
|
|
ASSERT_EQ(client_1, 1);
|
|
ASSERT_EQ(server_1, 1);
|
|
/* The regression: on unfixed code the provider-side run leaves server_2==0. */
|
|
ASSERT_EQ(client_2, 1);
|
|
ASSERT_EQ(server_2, 1);
|
|
ASSERT_EQ(client_3, 1);
|
|
ASSERT_EQ(server_3, 1);
|
|
PASS();
|
|
}
|
|
|
|
/* ── Suite ───────────────────────────────────────────────────────────────── */
|
|
SUITE(repro_issue523) {
|
|
RUN_TEST(repro_issue523_crossrepo_http_calls_edge);
|
|
RUN_TEST(repro_issue523_scheme_stripped_url_match);
|
|
RUN_TEST(repro_issue523_template_fuzzy_match);
|
|
RUN_TEST(repro_issue523_reverse_direction_match);
|
|
RUN_TEST(repro_issue523_idempotent_cross_edges);
|
|
}
|