mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
fix(pipeline): compose axios.create baseURL into HTTP_CALLS (#1916)
The Vue-admin wrapper pattern -- `const api = axios.create({ baseURL:
'/api' }); export default api` in one module, `api.get('/orders')` in
another -- produced no HTTP_CALLS edge, so cross-repo-intelligence found
nothing to link to the backend's GET /api/orders.
Root cause: nothing recorded that `api` is an axios instance. The call
`api.get('/orders')` fell through to the route-registration suffix
fallback (`.get` + a '/'-leading argument), which treats it as an Express
`app.get` REGISTRATION: a CALLS edge to a phantom server-side Route
`/orders` (via=route_registration), and never an HTTP_CALLS edge. The
base path was unknowable anyway.
Fix, reusing HTTP_CALLS/Route (no new labels):
- extraction (JS/TS/TSX/ArkTS): a module-level binding initialised by
`axios.create(...)` records http_client="axios" and the literal
`baseURL` (NULL when absent or non-literal, never guessed); the Module
def carries the same pair when its default export is such a client
(`export default api` / `export default axios.create(...)`). ES default
imports are flagged (CBMImport.is_default).
- both call resolvers (pass_calls.c, pass_parallel.c), before the #523
callee-spelling check and the route-registration fallback: a
`<recv>.<verb>(path)` whose receiver binds -- same file, named import, or
default import -- to a client emits HTTP_CALLS to base + path (axios
combineURLs: trailing '/' trimmed, absolute URLs kept as-is, path kept
when the base is unknown). Shared helper
cbm_pipeline_http_client_call_url keeps the two paths identical.
- incremental: the client pair joins the file's LSP surface (only for files
that have one, so every other surface sha is unchanged), so a baseURL
edit pulls importers into the closure; delta preseed proxies carry the
two keys so a caller re-resolved alone still sees its unchanged wrapper.
- result_compact relocates the two new strings (ASan caught the dangling
worker-arena pointer).
Look-alikes (`factory.create({ baseURL })`, a plain object's `.get`) are
unchanged.
Test: pipeline_axios_wrapper_baseurl_composes_http_calls_issue1916 --
default/named/same-file/dynamic-base/look-alike cases, sequential vs
parallel resolver equality, and same-DB closure-repair reindex (wrapper
edit, then caller-only edit) converging with fresh indexes. RED on origin/
main (seq.wrapper_http == 0); each of the parallel, surface and preseed
pieces reverted alone turns its own assertion RED.
Fixes #1916
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This commit is contained in:
1 parent
5958f546dc
commit
b9ad9ed144
13 files changed
+623
-22
No files matched your search
@@ -238,6 +238,14 @@ typedef struct {
|
||||
* that declared this method. Kept at the tail so zero-initialised
|
||||
* callers in every other language remain ABI/source compatible. */
|
||||
const char *impl_trait;
|
||||
/* JS/TS only (#1916): a module-level binding initialised by
|
||||
* `axios.create(...)` records the client library here ("axios"), and the
|
||||
* literal `baseURL` string (or NULL when absent / not a string literal).
|
||||
* The Module def carries the pair when its default export is such a
|
||||
* client. The call resolver composes `<binding>.get('/p')` into an
|
||||
* HTTP_CALLS edge to base + path. Tail fields: zero-init stays valid. */
|
||||
const char *http_client;
|
||||
const char *http_base_url;
|
||||
} CBMDefinition;
|
||||
|
||||
/* Argument captured from a call expression */
|
||||
@@ -299,6 +307,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
const char *local_name; // local alias or name
|
||||
const char *module_path; // resolved module path / QN
|
||||
bool is_default; // ES default import (`import X from "Y"`), JS/TS only (#1916)
|
||||
} CBMImport;
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -5744,6 +5744,123 @@ static bool is_require_import_call(TSNode value, const char *source, CBMArena *a
|
||||
return false;
|
||||
}
|
||||
|
||||
/* True when text of `node` equals `want` exactly (no arena allocation). */
|
||||
static bool js_node_text_is(TSNode node, const char *source, const char *want) {
|
||||
if (ts_node_is_null(node)) {
|
||||
return false;
|
||||
}
|
||||
uint32_t start = ts_node_start_byte(node);
|
||||
uint32_t end = ts_node_end_byte(node);
|
||||
size_t len = strlen(want);
|
||||
return end >= start && (size_t)(end - start) == len && memcmp(source + start, want, len) == 0;
|
||||
}
|
||||
|
||||
/* #1916: `axios.create(...)` — the factory of a configured axios instance
|
||||
* (the vue-element-admin / RuoYi `request.js` wrapper). Only the literal
|
||||
* `axios` receiver is recognised: a look-alike `factory.create(...)` is not
|
||||
* an HTTP client and must not turn its binding into one. */
|
||||
static bool js_is_axios_create_call(TSNode value, const char *source) {
|
||||
if (ts_node_is_null(value) || strcmp(ts_node_type(value), "call_expression") != 0) {
|
||||
return false;
|
||||
}
|
||||
TSNode fn = ts_node_child_by_field_name(value, TS_FIELD("function"));
|
||||
if (ts_node_is_null(fn) || strcmp(ts_node_type(fn), "member_expression") != 0) {
|
||||
return false;
|
||||
}
|
||||
TSNode obj = ts_node_child_by_field_name(fn, TS_FIELD("object"));
|
||||
TSNode prop = ts_node_child_by_field_name(fn, TS_FIELD("property"));
|
||||
return !ts_node_is_null(obj) && strcmp(ts_node_type(obj), "identifier") == 0 &&
|
||||
js_node_text_is(obj, source, "axios") && js_node_text_is(prop, source, "create");
|
||||
}
|
||||
|
||||
/* The literal `baseURL` of `axios.create({ baseURL: '<lit>' })`, or NULL when
|
||||
* the config is absent, not an object literal, or the value is not a plain
|
||||
* string literal (process.env.X, a template with substitutions, an escape):
|
||||
* an unknown base is never guessed. */
|
||||
static const char *js_axios_create_base_url(CBMArena *a, TSNode call, const char *source) {
|
||||
TSNode args = ts_node_child_by_field_name(call, TS_FIELD("arguments"));
|
||||
if (ts_node_is_null(args) || ts_node_named_child_count(args) == 0) {
|
||||
return NULL;
|
||||
}
|
||||
TSNode cfg = ts_node_named_child(args, 0);
|
||||
if (strcmp(ts_node_type(cfg), "object") != 0) {
|
||||
return NULL;
|
||||
}
|
||||
uint32_t n = ts_node_named_child_count(cfg);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
TSNode pair = ts_node_named_child(cfg, i);
|
||||
if (strcmp(ts_node_type(pair), "pair") != 0) {
|
||||
continue;
|
||||
}
|
||||
TSNode key = ts_node_child_by_field_name(pair, TS_FIELD("key"));
|
||||
if (!js_node_text_is(key, source, "baseURL") &&
|
||||
!js_node_text_is(key, source, "'baseURL'") &&
|
||||
!js_node_text_is(key, source, "\"baseURL\"")) {
|
||||
continue;
|
||||
}
|
||||
TSNode val = ts_node_child_by_field_name(pair, TS_FIELD("value"));
|
||||
if (ts_node_is_null(val) || strcmp(ts_node_type(val), "string") != 0) {
|
||||
return NULL;
|
||||
}
|
||||
char *text = cbm_node_text(a, val, source);
|
||||
size_t len = text ? strlen(text) : 0;
|
||||
if (len < PAIR_LEN || strchr(text, '\\') != NULL) {
|
||||
return NULL;
|
||||
}
|
||||
text[len - SKIP_ONE] = '\0';
|
||||
return text + SKIP_ONE;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Mark `def` as an axios client instance created by `call` (#1916). */
|
||||
static void js_mark_axios_client(CBMArena *a, CBMDefinition *def, TSNode call, const char *source) {
|
||||
def->http_client = "axios";
|
||||
def->http_base_url = js_axios_create_base_url(a, call, source);
|
||||
}
|
||||
|
||||
/* #1916: `export default api;` (api an axios instance declared above) or
|
||||
* `export default axios.create({...})` makes the MODULE's default export the
|
||||
* client — record it on the Module def so a default import can find it. */
|
||||
static void js_mark_default_export_client(CBMExtractCtx *ctx, int mod_idx) {
|
||||
if (mod_idx < 0 || mod_idx >= ctx->result->defs.count) {
|
||||
return;
|
||||
}
|
||||
TSTreeCursor cursor = ts_tree_cursor_new(ctx->root);
|
||||
if (!ts_tree_cursor_goto_first_child(&cursor)) {
|
||||
ts_tree_cursor_delete(&cursor);
|
||||
return;
|
||||
}
|
||||
do {
|
||||
TSNode stmt = ts_tree_cursor_current_node(&cursor);
|
||||
if (strcmp(ts_node_type(stmt), "export_statement") != 0) {
|
||||
continue;
|
||||
}
|
||||
TSNode val = ts_node_child_by_field_name(stmt, TS_FIELD("value"));
|
||||
if (ts_node_is_null(val)) {
|
||||
continue;
|
||||
}
|
||||
CBMDefinition *mod = &ctx->result->defs.items[mod_idx];
|
||||
if (js_is_axios_create_call(val, ctx->source)) {
|
||||
js_mark_axios_client(ctx->arena, mod, val, ctx->source);
|
||||
continue;
|
||||
}
|
||||
if (strcmp(ts_node_type(val), "identifier") != 0) {
|
||||
continue;
|
||||
}
|
||||
for (int d = 0; d < ctx->result->defs.count; d++) {
|
||||
const CBMDefinition *v = &ctx->result->defs.items[d];
|
||||
if (v->http_client && v->label && strcmp(v->label, "Variable") == 0 && v->name &&
|
||||
!v->parent_class && js_node_text_is(val, ctx->source, v->name)) {
|
||||
mod->http_client = v->http_client;
|
||||
mod->http_base_url = v->http_base_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (ts_tree_cursor_goto_next_sibling(&cursor));
|
||||
ts_tree_cursor_delete(&cursor);
|
||||
}
|
||||
|
||||
// JS/TS variable extraction: skip function-assigned declarators.
|
||||
static void extract_js_vars(CBMExtractCtx *ctx, TSNode node, CBMArena *a) {
|
||||
uint32_t n = ts_node_named_child_count(node);
|
||||
@@ -5779,7 +5896,12 @@ static void extract_js_vars(CBMExtractCtx *ctx, TSNode node, CBMArena *a) {
|
||||
if (is_require) {
|
||||
continue;
|
||||
}
|
||||
int before = ctx->result->defs.count;
|
||||
push_var_def(ctx, cbm_node_text(a, vname, ctx->source), child);
|
||||
if (ctx->result->defs.count > before &&
|
||||
js_is_axios_create_call(value, ctx->source)) {
|
||||
js_mark_axios_client(a, &ctx->result->defs.items[before], value, ctx->source);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8212,7 +8334,14 @@ void cbm_extract_definitions(CBMExtractCtx *ctx) {
|
||||
mod.route_method = "GET"; /* a routable page is reached by navigation */
|
||||
}
|
||||
}
|
||||
int mod_idx = ctx->result->defs.count;
|
||||
cbm_defs_push(&ctx->result->defs, a, mod);
|
||||
|
||||
cbm_extract_definitions_without_module(ctx);
|
||||
|
||||
if (ctx->language == CBM_LANG_JAVASCRIPT || ctx->language == CBM_LANG_TYPESCRIPT ||
|
||||
ctx->language == CBM_LANG_TSX || ctx->language == CBM_LANG_ARKTS) {
|
||||
/* Same language set as extract_js_vars, which marks the bindings. */
|
||||
js_mark_default_export_client(ctx, mod_idx);
|
||||
}
|
||||
}
|
||||
@@ -394,7 +394,7 @@ static bool process_import_clause(CBMExtractCtx *ctx, TSNode clause, const char
|
||||
const char *sk = ts_node_type(sub);
|
||||
if (strcmp(sk, "identifier") == 0) {
|
||||
char *name = cbm_node_text(a, sub, ctx->source);
|
||||
CBMImport imp = {.local_name = name, .module_path = path};
|
||||
CBMImport imp = {.local_name = name, .module_path = path, .is_default = true};
|
||||
cbm_imports_push(&ctx->result->imports, a, imp);
|
||||
found = true;
|
||||
} else if (strcmp(sk, "namespace_import") == 0) {
|
||||
|
||||
@@ -273,6 +273,8 @@ static void cr_walk_def(cr_ctx_t *c, CBMDefinition *d) {
|
||||
cr_str(c, &d->structural_profile);
|
||||
cr_str(c, &d->body_tokens);
|
||||
cr_str(c, &d->impl_trait);
|
||||
cr_str(c, &d->http_client);
|
||||
cr_str(c, &d->http_base_url);
|
||||
}
|
||||
|
||||
static void cr_walk_call(cr_ctx_t *c, CBMCall *call) {
|
||||
|
||||
@@ -125,6 +125,31 @@ static char *surface_file_to_json(const CBMFileResult *result, const CBMLSPDef *
|
||||
}
|
||||
yyjson_mut_obj_add_val(doc, root, "reg", reg);
|
||||
|
||||
/* #1916: an axios instance binding's baseURL is consumed by the files
|
||||
* that import it (their HTTP_CALLS compose base + path), so a changed
|
||||
* base must change the surface or those importers keep stale edges.
|
||||
* Written only when the file has such a binding: every other file's
|
||||
* surface bytes — and so its sha — stay exactly what they were. The
|
||||
* decoder ignores this key; it feeds the early-cutoff hash only. */
|
||||
yyjson_mut_val *http = NULL;
|
||||
for (int i = 0; result && i < result->defs.count; i++) {
|
||||
const CBMDefinition *d = &result->defs.items[i];
|
||||
if (!d->http_client || !d->qualified_name) {
|
||||
continue;
|
||||
}
|
||||
if (!http) {
|
||||
http = yyjson_mut_arr(doc);
|
||||
}
|
||||
yyjson_mut_val *o = yyjson_mut_obj(doc);
|
||||
yyjson_mut_obj_add_str(doc, o, "q", d->qualified_name);
|
||||
yyjson_mut_obj_add_str(doc, o, "c", d->http_client);
|
||||
add_str_or_null(doc, o, "b", d->http_base_url);
|
||||
yyjson_mut_arr_add_val(http, o);
|
||||
}
|
||||
if (http) {
|
||||
yyjson_mut_obj_add_val(doc, root, "http", http);
|
||||
}
|
||||
|
||||
char *json = yyjson_mut_write(doc, 0, out_len);
|
||||
yyjson_mut_doc_free(doc);
|
||||
return json;
|
||||
|
||||
+189
-7
@@ -204,6 +204,169 @@ static void free_import_map(const char **keys, const char **vals, int count) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ── #1916: axios client-instance calls ─────────────────────────── */
|
||||
|
||||
/* Copy the plain (unescaped) JSON string value of "key" in props into out.
|
||||
* False when absent, escaped, or too long — an unreadable base is unknown. */
|
||||
static bool pc_json_str_prop(const char *props, const char *key, char *out, size_t out_sz) {
|
||||
if (!props) {
|
||||
return false;
|
||||
}
|
||||
char pat[CBM_SZ_64];
|
||||
snprintf(pat, sizeof(pat), "\"%s\":\"", key);
|
||||
const char *p = strstr(props, pat);
|
||||
if (!p) {
|
||||
return false;
|
||||
}
|
||||
p += strlen(pat);
|
||||
const char *e = strchr(p, '"');
|
||||
if (!e) {
|
||||
return false;
|
||||
}
|
||||
size_t len = (size_t)(e - p);
|
||||
if (len >= out_sz || memchr(p, '\\', len) != NULL) {
|
||||
return false;
|
||||
}
|
||||
memcpy(out, p, len);
|
||||
out[len] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
/* A node the extractor marked as an axios instance. `base` receives the
|
||||
* literal baseURL ("" when unknown). */
|
||||
static bool pc_node_is_http_client(const cbm_gbuf_node_t *n, char *base, size_t base_sz) {
|
||||
char client[CBM_SZ_32];
|
||||
if (!n || !pc_json_str_prop(n->properties_json, "http_client", client, sizeof(client))) {
|
||||
return false;
|
||||
}
|
||||
if (!pc_json_str_prop(n->properties_json, "http_base_url", base, base_sz)) {
|
||||
base[0] = '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* The module-level Variable `name` declared in file `file_rel`. */
|
||||
static const cbm_gbuf_node_t *pc_module_var(const cbm_gbuf_t *gbuf, const char *project,
|
||||
const char *file_rel, const char *name) {
|
||||
if (!file_rel) {
|
||||
return NULL;
|
||||
}
|
||||
char *qn = cbm_pipeline_fqn_compute(project, file_rel, name);
|
||||
const cbm_gbuf_node_t *n = qn ? cbm_gbuf_find_by_qn(gbuf, qn) : NULL;
|
||||
free(qn);
|
||||
return (n && n->label && strcmp(n->label, "Variable") == 0) ? n : NULL;
|
||||
}
|
||||
|
||||
/* Resolve the binding `recv` names in the calling file to a client node:
|
||||
* a same-file declaration first, then its ES import (default import → the
|
||||
* module's default export; named import → that module's Variable). */
|
||||
static bool pc_receiver_client_base(const cbm_gbuf_t *gbuf, const char *project, const char *rel,
|
||||
const CBMFileResult *result, const char **imp_keys,
|
||||
const char **imp_vals, int imp_count, const char *recv,
|
||||
char *base, size_t base_sz) {
|
||||
if (pc_node_is_http_client(pc_module_var(gbuf, project, rel, recv), base, base_sz)) {
|
||||
return true;
|
||||
}
|
||||
const CBMImport *imp = NULL;
|
||||
for (int i = 0; result && i < result->imports.count; i++) {
|
||||
const CBMImport *it = &result->imports.items[i];
|
||||
if (it->local_name && strcmp(it->local_name, recv) == 0) {
|
||||
imp = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!imp) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < imp_count; i++) {
|
||||
if (!imp_keys[i] || strcmp(imp_keys[i], recv) != 0 || !imp_vals[i]) {
|
||||
continue;
|
||||
}
|
||||
const cbm_gbuf_node_t *t = cbm_gbuf_find_by_qn(gbuf, imp_vals[i]);
|
||||
if (!t || !t->label) {
|
||||
return false;
|
||||
}
|
||||
if (strcmp(t->label, "Variable") == 0) {
|
||||
return pc_node_is_http_client(t, base, base_sz);
|
||||
}
|
||||
if (strcmp(t->label, "Module") != 0) {
|
||||
return false;
|
||||
}
|
||||
if (imp->is_default) {
|
||||
return pc_node_is_http_client(t, base, base_sz);
|
||||
}
|
||||
return pc_node_is_http_client(pc_module_var(gbuf, project, t->file_path, recv), base,
|
||||
base_sz);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* axios instance request methods (the verb after the receiver). */
|
||||
static bool pc_is_axios_verb(const char *verb) {
|
||||
static const char *const verbs[] = {"get", "post", "put", "delete",
|
||||
"patch", "head", "options", NULL};
|
||||
for (int i = 0; verbs[i]; i++) {
|
||||
if (strcmp(verb, verbs[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Copy the plain-identifier receiver of `callee` (text before `dot`) into
|
||||
* recv. False for `this.api.get`, `a().get` and other non-binding receivers. */
|
||||
static bool pc_plain_receiver(const char *callee, const char *dot, char *recv, size_t recv_sz) {
|
||||
size_t rlen = (size_t)(dot - callee);
|
||||
if (rlen == 0 || rlen >= recv_sz) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < rlen; i++) {
|
||||
char c = callee[i];
|
||||
bool ident = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') ||
|
||||
c == '_' || c == '$';
|
||||
if (!ident) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
memcpy(recv, callee, rlen);
|
||||
recv[rlen] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cbm_pipeline_http_client_call_url(const cbm_gbuf_t *gbuf, const char *project, const char *rel,
|
||||
const CBMFileResult *result, const char **imp_keys,
|
||||
const char **imp_vals, int imp_count, const CBMCall *call,
|
||||
char *out, size_t out_sz) {
|
||||
const char *callee = call ? call->callee_name : NULL;
|
||||
const char *url = call ? call->first_string_arg : NULL;
|
||||
if (!gbuf || !project || !callee || !url || (url[0] != '/' && !strstr(url, "://"))) {
|
||||
return false;
|
||||
}
|
||||
const char *dot = strrchr(callee, '.');
|
||||
char recv[CBM_SZ_128];
|
||||
if (!dot || !pc_is_axios_verb(dot + SKIP_ONE) ||
|
||||
!pc_plain_receiver(callee, dot, recv, sizeof(recv))) {
|
||||
return false;
|
||||
}
|
||||
char base[CBM_SZ_256];
|
||||
if (!pc_receiver_client_base(gbuf, project, rel, result, imp_keys, imp_vals, imp_count, recv,
|
||||
base, sizeof(base))) {
|
||||
return false;
|
||||
}
|
||||
/* axios combineURLs: an absolute request URL ignores baseURL; otherwise
|
||||
* the base (trailing '/' trimmed) is joined with the '/'-leading path. */
|
||||
if (strstr(url, "://") || base[0] == '\0') {
|
||||
int n = snprintf(out, out_sz, "%s", url);
|
||||
return n > 0 && (size_t)n < out_sz;
|
||||
}
|
||||
size_t blen = strlen(base);
|
||||
while (blen > 0 && base[blen - SKIP_ONE] == '/') {
|
||||
base[--blen] = '\0';
|
||||
}
|
||||
int n = snprintf(out, out_sz, "%s%s", base, url);
|
||||
return n > 0 && (size_t)n < out_sz;
|
||||
}
|
||||
|
||||
/* Handle a route registration call: create Route node + HANDLES edge. */
|
||||
static void handle_route_registration(cbm_pipeline_ctx_t *ctx, const CBMCall *call,
|
||||
const cbm_gbuf_node_t *source_node, const char *module_qn,
|
||||
@@ -475,10 +638,10 @@ static const cbm_gbuf_node_t *calls_find_source(cbm_pipeline_ctx_t *ctx, const c
|
||||
}
|
||||
|
||||
/* Resolve one call and emit the appropriate edge. Returns 1 if resolved, 0 if not. */
|
||||
static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call,
|
||||
const CBMResolvedCallArray *lsp_calls, const char *rel,
|
||||
const char *module_qn, const char **imp_keys, const char **imp_vals,
|
||||
int imp_count, CBMLanguage lang) {
|
||||
static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBMFileResult *result,
|
||||
const char *rel, const char *module_qn, const char **imp_keys,
|
||||
const char **imp_vals, int imp_count, CBMLanguage lang) {
|
||||
const CBMResolvedCallArray *lsp_calls = &result->resolved_calls;
|
||||
const cbm_gbuf_node_t *source_node = calls_find_source(ctx, rel, call->enclosing_func_qn);
|
||||
if (!source_node) {
|
||||
return 0;
|
||||
@@ -527,7 +690,26 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call,
|
||||
* and not an HTTP pattern, so BOTH the empty-resolution and resolved-QN
|
||||
* service checks below miss it and the call is dropped. Detect it on the
|
||||
* callee_name FIRST so the HTTP_CALLS/ASYNC_CALLS edge is emitted regardless
|
||||
* (target is a synthesized route node, not the unindexed library). (#523) */
|
||||
* (target is a synthesized route node, not the unindexed library). (#523)
|
||||
*
|
||||
* First, though: `api.get('/orders')` on an axios.create() instance (#1916).
|
||||
* The receiver's binding decides, not its spelling — without this the
|
||||
* verb suffix made it an Express route REGISTRATION below (a CALLS edge
|
||||
* to a phantom server route), and a receiver spelled `axiosInstance`
|
||||
* substring-matches the #523 check and loses its baseURL. */
|
||||
char client_url[CBM_SZ_512];
|
||||
if (cbm_pipeline_http_client_call_url(ctx->gbuf, ctx->project_name, rel, result, imp_keys,
|
||||
imp_vals, imp_count, call, client_url,
|
||||
sizeof(client_url))) {
|
||||
CBMCall routed = *call;
|
||||
routed.first_string_arg = client_url;
|
||||
cbm_resolution_t svc_res = {.qualified_name = call->callee_name,
|
||||
.confidence = PC_SVC_PATTERN_CONF,
|
||||
.strategy = "http_client_instance",
|
||||
.candidate_count = 0};
|
||||
emit_http_async_edge(ctx, &routed, source_node, NULL, &svc_res, CBM_SVC_HTTP, false);
|
||||
return SKIP_ONE;
|
||||
}
|
||||
cbm_svc_kind_t csvc = cbm_service_pattern_match(call->callee_name);
|
||||
if (csvc == CBM_SVC_HTTP || csvc == CBM_SVC_ASYNC) {
|
||||
const char *cu = call->first_string_arg;
|
||||
@@ -841,8 +1023,8 @@ int cbm_pipeline_pass_calls(cbm_pipeline_ctx_t *ctx, const cbm_file_info_t *file
|
||||
continue;
|
||||
}
|
||||
total_calls++;
|
||||
if (resolve_single_call(ctx, call, &result->resolved_calls, rel, module_qn, imp_keys,
|
||||
imp_vals, imp_count, files[i].language)) {
|
||||
if (resolve_single_call(ctx, call, result, rel, module_qn, imp_keys, imp_vals,
|
||||
imp_count, files[i].language)) {
|
||||
resolved++;
|
||||
} else {
|
||||
unresolved++;
|
||||
|
||||
@@ -294,6 +294,8 @@ static void build_def_props(char *buf, size_t bufsize, const CBMDefinition *def)
|
||||
append_json_str_array(buf, bufsize, &pos, "param_types", def->param_types);
|
||||
append_json_string(buf, bufsize, &pos, "route_path", def->route_path);
|
||||
append_json_string(buf, bufsize, &pos, "route_method", def->route_method);
|
||||
append_json_string(buf, bufsize, &pos, "http_client", def->http_client);
|
||||
append_json_string(buf, bufsize, &pos, "http_base_url", def->http_base_url);
|
||||
|
||||
/* MinHash fingerprint — append if present and buffer has room. */
|
||||
if (def->fingerprint && def->fingerprint_k > 0 &&
|
||||
|
||||
@@ -522,6 +522,8 @@ static void build_def_props(char *buf, size_t bufsize, const CBMDefinition *def)
|
||||
append_json_str_array(buf, bufsize, &pos, "param_types", def->param_types);
|
||||
append_json_string(buf, bufsize, &pos, "route_path", def->route_path);
|
||||
append_json_string(buf, bufsize, &pos, "route_method", def->route_method);
|
||||
append_json_string(buf, bufsize, &pos, "http_client", def->http_client);
|
||||
append_json_string(buf, bufsize, &pos, "http_base_url", def->http_base_url);
|
||||
|
||||
/* MinHash fingerprint — append if present and buffer has room.
|
||||
* Hex-encoded K=64 uint32 = 512 chars + key/quotes ≈ 520 chars. */
|
||||
@@ -2966,7 +2968,22 @@ static void resolve_file_calls(resolve_ctx_t *rc, resolve_worker_state_t *ws, CB
|
||||
* pattern, so the resolved-QN service checks below miss it and the call
|
||||
* is dropped. Detect it on the callee_name FIRST so the HTTP_CALLS/
|
||||
* ASYNC_CALLS edge is emitted regardless (target is a synthesized route
|
||||
* node, not the unindexed library). Mirrors pass_calls.c. (#523) */
|
||||
* node, not the unindexed library). Mirrors pass_calls.c. (#523)
|
||||
*
|
||||
* First: a call on an axios.create() instance (#1916) — decided by the
|
||||
* receiver's binding, before the #523 spelling check and the
|
||||
* route-registration suffix fallback. MUST match pass_calls.c. */
|
||||
char client_url[CBM_SZ_512];
|
||||
if (cbm_pipeline_http_client_call_url(rc->main_gbuf, rc->project_name, rel, result,
|
||||
imp_keys, imp_vals, imp_count, call, client_url,
|
||||
sizeof(client_url))) {
|
||||
cbm_resolution_t svc_res = {.qualified_name = call->callee_name,
|
||||
.confidence = PP_HALF_CONF,
|
||||
.strategy = "http_client_instance"};
|
||||
emit_http_async_service_edge(ws->local_edge_buf, source_node, call, &svc_res,
|
||||
CBM_SVC_HTTP, client_url);
|
||||
continue;
|
||||
}
|
||||
cbm_svc_kind_t csvc = cbm_service_pattern_match(call->callee_name);
|
||||
if (csvc == CBM_SVC_HTTP || csvc == CBM_SVC_ASYNC) {
|
||||
const char *cu = call->first_string_arg;
|
||||
|
||||
@@ -1180,7 +1180,7 @@ static void log_result_census(const char *tag, CBMFileResult **cache, int file_c
|
||||
}
|
||||
str_def_fp += def->fingerprint ? (size_t)def->fingerprint_k * sizeof(uint32_t) : 0;
|
||||
str_def_misc += census_len(def->route_path) + census_len(def->route_method) +
|
||||
census_len(def->impl_trait);
|
||||
census_len(def->impl_trait) + census_len(def->http_base_url);
|
||||
}
|
||||
for (int c = 0; c < r->calls.count; c++) {
|
||||
const CBMCall *call = &r->calls.items[c];
|
||||
|
||||
@@ -287,13 +287,25 @@ int64_t cbm_delta_preseed(cbm_store_t *store, const char *project, cbm_gbuf_t *g
|
||||
* UNIQUE violation the pre-remap patch would have raised. A resolver that
|
||||
* only LOOKS UP still needs its target resident, which is why the list
|
||||
* mirrors the registry's own membership rule instead of guessing. */
|
||||
/* The one property a resolver READS off a proxy: an axios instance
|
||||
* binding's client + baseURL (#1916). A caller re-resolved alone must
|
||||
* compose `api.get('/p')` against its unchanged wrapper exactly as a full
|
||||
* build does. Only the two keys, only on the rare Module/Variable rows
|
||||
* that carry them; every other proxy stays "{}". Proxies are never
|
||||
* written back (cbm_delta_patch skips id <= max_db_id). */
|
||||
sqlite3_stmt *stmt = NULL;
|
||||
if (sqlite3_prepare_v2(db,
|
||||
"SELECT id, label, name, qualified_name, file_path FROM nodes"
|
||||
" WHERE project = ?1 AND label NOT IN"
|
||||
" ('Macro','Comment','Section','Branch','Commit','Tag')"
|
||||
" ORDER BY id",
|
||||
CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK) {
|
||||
if (sqlite3_prepare_v2(
|
||||
db,
|
||||
"SELECT id, label, name, qualified_name, file_path,"
|
||||
" CASE WHEN label IN ('Module','Variable')"
|
||||
" AND instr(properties, '\"http_client\"') > 0"
|
||||
" THEN json_object('http_client', json_extract(properties, '$.http_client'),"
|
||||
" 'http_base_url', json_extract(properties, '$.http_base_url')) END"
|
||||
" FROM nodes"
|
||||
" WHERE project = ?1 AND label NOT IN"
|
||||
" ('Macro','Comment','Section','Branch','Commit','Tag')"
|
||||
" ORDER BY id",
|
||||
CBM_NOT_FOUND, &stmt, NULL) != SQLITE_OK) {
|
||||
return -1;
|
||||
}
|
||||
sqlite3_bind_text(stmt, 1, project, CBM_NOT_FOUND, SQLITE_TRANSIENT);
|
||||
@@ -305,9 +317,11 @@ int64_t cbm_delta_preseed(cbm_store_t *store, const char *project, cbm_gbuf_t *g
|
||||
const char *name = (const char *)sqlite3_column_text(stmt, 2);
|
||||
const char *qn = (const char *)sqlite3_column_text(stmt, 3);
|
||||
const char *fp = (const char *)sqlite3_column_text(stmt, 4);
|
||||
const char *client_props = (const char *)sqlite3_column_text(stmt, 5);
|
||||
/* Pin the gbuf id to the database id: proxies ARE their rows. */
|
||||
cbm_gbuf_set_next_id(gbuf, id);
|
||||
int64_t got = cbm_gbuf_upsert_node(gbuf, label, name, qn, fp ? fp : "", 0, 0, "{}");
|
||||
int64_t got = cbm_gbuf_upsert_node(gbuf, label, name, qn, fp ? fp : "", 0, 0,
|
||||
client_props ? client_props : "{}");
|
||||
if (got != id) {
|
||||
/* A QN collision inside the preseed set would silently split
|
||||
* identity between RAM and disk; the run cannot be trusted. */
|
||||
|
||||
@@ -211,6 +211,21 @@ void cbm_pipeline_set_pkgmap(CBMHashTable *map);
|
||||
char *cbm_pipeline_resolve_module(const cbm_pipeline_ctx_t *ctx, const char *source_rel,
|
||||
const char *module_path);
|
||||
|
||||
/* #1916: HTTP client-instance calls. When `call` is `<recv>.<verb>(url)` with
|
||||
* an HTTP verb suffix and a path/URL first argument, and `recv` is bound —
|
||||
* in the calling module itself, or via an ES import (named or default) — to a
|
||||
* binding the extractor marked as an `axios.create(...)` instance
|
||||
* (`http_client` node property), write the request URL to `out` (the
|
||||
* instance's literal `http_base_url` joined with a '/'-leading path, the path
|
||||
* unchanged when the base is unknown or the URL is absolute) and return true.
|
||||
* Both call resolvers (pass_calls.c, pass_parallel.c) call this before any
|
||||
* route-registration or registry fallback, so a wrapper client's `api.get`
|
||||
* is never mistaken for an Express `app.get` route registration. */
|
||||
bool cbm_pipeline_http_client_call_url(const cbm_gbuf_t *gbuf, const char *project, const char *rel,
|
||||
const CBMFileResult *result, const char **imp_keys,
|
||||
const char **imp_vals, int imp_count, const CBMCall *call,
|
||||
char *out, size_t out_sz);
|
||||
|
||||
/* Resolve an import to its in-graph target node, or NULL if unresolvable.
|
||||
*
|
||||
* Resolution order (first hit wins):
|
||||
|
||||
@@ -181,11 +181,12 @@ TEST(extract_ts_factory_object_methods_issue341) {
|
||||
*
|
||||
* The bound is derived, not tuned. Measured on this source, total_alloc was
|
||||
* 365984 before the scratch arena and is 87456 after, exactly that difference.
|
||||
* Of the 87456 that remain, 7680 is the defs item array at GROW_ARRAY's
|
||||
* starting capacity of 32 times sizeof(CBMDefinition) 240, and the other 79776
|
||||
* is everything else this file's extraction interns; none of it is traversal
|
||||
* scratch. So the bound sits above 87456 with room and a factor of four below
|
||||
* 365984.
|
||||
* #1916 added two pointers to CBMDefinition (240 -> 256 bytes), so it now
|
||||
* measures 87968: 8192 is the defs item array at GROW_ARRAY's starting
|
||||
* capacity of 32 times sizeof(CBMDefinition) 256, and the other 79776 is
|
||||
* everything else this file's extraction interns; none of it is traversal
|
||||
* scratch. So the bound sits above 87968 with room and a factor of four below
|
||||
* the 365984 the scratch stacks cost.
|
||||
*
|
||||
* It is a byte budget, not a proof of lifetime; that is
|
||||
* extract_traversal_stacks_come_from_ctx_scratch_issue2010 in test_mem.c. */
|
||||
|
||||
@@ -5585,6 +5585,210 @@ TEST(pipeline_incremental_parallel_result_cache_alloc_failure_preserves_db_and_r
|
||||
}
|
||||
#endif
|
||||
|
||||
/* #1916: one index of the axios-wrapper fixture, reduced to the edge counts
|
||||
* the test asserts. */
|
||||
typedef struct {
|
||||
int run_rc;
|
||||
int wrapper_http; /* loadWrapperOrders -HTTP_CALLS-> <base>/orders */
|
||||
int wrapper_http_v3; /* same call after the wrapper's base moves to /v3 */
|
||||
int wrapper_http_v3_inv; /* ... and then the caller's path moves to /invoices */
|
||||
int wrapper_raw_http; /* loadWrapperOrders -HTTP_CALLS-> /orders (uncomposed) */
|
||||
int wrapper_route_reg; /* loadWrapperOrders -CALLS-> /orders (phantom route reg) */
|
||||
int named_http; /* named import, base with trailing '/' */
|
||||
int local_http; /* same-file instance spelled axiosInstance */
|
||||
int local_raw_http; /* ... uncomposed (the #523 spelling match) */
|
||||
int dynamic_http; /* non-literal baseURL: path kept, never guessed */
|
||||
int dynamic_route_reg; /* ... misread as a route registration */
|
||||
int direct_http; /* control: axios.get('/direct') */
|
||||
int fake_http; /* look-alike factory.create: never a client */
|
||||
int provider_handles; /* control: Express app.get stays a registration */
|
||||
} AxiosWrapperObs;
|
||||
|
||||
static AxiosWrapperObs observe_axios_wrapper(const char *repo, const char *db) {
|
||||
AxiosWrapperObs o;
|
||||
memset(&o, 0xff, sizeof(o)); /* -1 everywhere: "not observed" */
|
||||
cbm_pipeline_t *p = cbm_pipeline_new(repo, db, CBM_MODE_FULL);
|
||||
if (!p) {
|
||||
return o;
|
||||
}
|
||||
o.run_rc = cbm_pipeline_run(p);
|
||||
const char *proj = cbm_pipeline_project_name(p);
|
||||
cbm_store_t *s = cbm_store_open_path(db);
|
||||
if (s && proj) {
|
||||
o.wrapper_http =
|
||||
named_edge_count(s, proj, "HTTP_CALLS", "loadWrapperOrders", "/api/orders");
|
||||
o.wrapper_http_v3 =
|
||||
named_edge_count(s, proj, "HTTP_CALLS", "loadWrapperOrders", "/v3/orders");
|
||||
o.wrapper_http_v3_inv =
|
||||
named_edge_count(s, proj, "HTTP_CALLS", "loadWrapperOrders", "/v3/invoices");
|
||||
o.wrapper_raw_http =
|
||||
named_edge_count(s, proj, "HTTP_CALLS", "loadWrapperOrders", "/orders");
|
||||
o.wrapper_route_reg = named_edge_count(s, proj, "CALLS", "loadWrapperOrders", "/orders");
|
||||
o.named_http = named_edge_count(s, proj, "HTTP_CALLS", "loadNamedItems", "/v2/items");
|
||||
o.local_http = named_edge_count(s, proj, "HTTP_CALLS", "loadLocal", "/local/x");
|
||||
o.local_raw_http = named_edge_count(s, proj, "HTTP_CALLS", "loadLocal", "/x");
|
||||
o.dynamic_http = named_edge_count(s, proj, "HTTP_CALLS", "loadDynamic", "/dynamic");
|
||||
o.dynamic_route_reg = named_edge_count(s, proj, "CALLS", "loadDynamic", "/dynamic");
|
||||
o.direct_http = named_edge_count(s, proj, "HTTP_CALLS", "loadDirectOrders", "/direct");
|
||||
o.fake_http = named_edge_count(s, proj, "HTTP_CALLS", "loadFake", "/api/fake");
|
||||
o.provider_handles = named_edge_count(s, proj, "HANDLES", "provideOrders", "/api/orders");
|
||||
}
|
||||
if (s) {
|
||||
cbm_store_close(s);
|
||||
}
|
||||
cbm_pipeline_free(p);
|
||||
return o;
|
||||
}
|
||||
|
||||
/* #1916: `const api = axios.create({ baseURL: '/api' }); export default api`
|
||||
* in one module and `api.get('/orders')` in another is THE Vue-admin wrapper
|
||||
* pattern. The call must become HTTP_CALLS to GET /api/orders — before the
|
||||
* fix the `.get` suffix made it an Express route REGISTRATION (a CALLS edge
|
||||
* to a phantom server route /orders) and cross-repo matching found nothing.
|
||||
* 52 fillers put the default run on the parallel resolver; the
|
||||
* single-thread run is the sequential oracle; a same-DB reindex after the
|
||||
* wrapper's base changes must converge with the fresh index. */
|
||||
TEST(pipeline_axios_wrapper_baseurl_composes_http_calls_issue1916) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_axios_wrapper_XXXXXX");
|
||||
if (!cbm_mkdtemp(tmp)) {
|
||||
FAIL("tmpdir");
|
||||
}
|
||||
write_temp_file(tmp, "src/client.ts",
|
||||
"import axios from 'axios';\n"
|
||||
"const api = axios.create({ baseURL: '/api', timeout: 5000 });\n"
|
||||
"export default api;\n");
|
||||
write_temp_file(
|
||||
tmp, "src/orders.ts",
|
||||
"import api from './client';\n"
|
||||
"import axios from 'axios';\n"
|
||||
"export function loadWrapperOrders(): unknown { return api.get('/orders'); }\n"
|
||||
"export function loadDirectOrders(): unknown { return axios.get('/direct'); }\n");
|
||||
write_temp_file(tmp, "src/named-client.ts",
|
||||
"import axios from 'axios';\n"
|
||||
"export const http = axios.create({ baseURL: '/v2/' });\n");
|
||||
write_temp_file(tmp, "src/named-caller.ts",
|
||||
"import { http } from './named-client';\n"
|
||||
"export function loadNamedItems(): unknown { return http.post('/items'); }\n");
|
||||
write_temp_file(tmp, "src/local.ts",
|
||||
"import axios from 'axios';\n"
|
||||
"const axiosInstance = axios.create({ baseURL: '/local' });\n"
|
||||
"export function loadLocal(): unknown { return axiosInstance.get('/x'); }\n");
|
||||
write_temp_file(tmp, "src/dynamic-client.ts",
|
||||
"import axios from 'axios';\n"
|
||||
"const dynamicApi = axios.create({ baseURL: process.env.API_BASE });\n"
|
||||
"export default dynamicApi;\n");
|
||||
write_temp_file(
|
||||
tmp, "src/dynamic-caller.ts",
|
||||
"import dynamicApi from './dynamic-client';\n"
|
||||
"export function loadDynamic(): unknown { return dynamicApi.get('/dynamic'); }\n");
|
||||
write_temp_file(tmp, "src/fake-client.ts",
|
||||
"const factory = { create: (config: unknown) => config };\n"
|
||||
"const fakeApi = factory.create({ baseURL: '/api' });\n"
|
||||
"export default fakeApi;\n");
|
||||
write_temp_file(tmp, "src/fake-caller.ts",
|
||||
"import fakeApi from './fake-client';\n"
|
||||
"export function loadFake(): unknown { return fakeApi.get('/fake'); }\n");
|
||||
write_temp_file(tmp, "src/provider.ts",
|
||||
"import express from 'express';\n"
|
||||
"const app = express();\n"
|
||||
"function provideOrders(): void {}\n"
|
||||
"app.get('/api/orders', provideOrders);\n");
|
||||
for (int i = 0; i < 52; i++) {
|
||||
char name[64];
|
||||
char body[128];
|
||||
snprintf(name, sizeof(name), "src/wrapper_pad_%02d.ts", i);
|
||||
snprintf(body, sizeof(body), "export function wrapperPad%02d(): number { return %d; }\n", i,
|
||||
i);
|
||||
write_temp_file(tmp, name, body);
|
||||
}
|
||||
|
||||
char *old_workers = getenv("CBM_WORKERS");
|
||||
char *saved_workers = old_workers ? strdup(old_workers) : NULL;
|
||||
char *old_single = getenv("CBM_INDEX_SINGLE_THREAD");
|
||||
char *saved_single = old_single ? strdup(old_single) : NULL;
|
||||
|
||||
char db_seq[512];
|
||||
char db_par[512];
|
||||
char db_fresh[512];
|
||||
char db_fresh2[512];
|
||||
snprintf(db_seq, sizeof(db_seq), "%s/seq.db", tmp);
|
||||
snprintf(db_par, sizeof(db_par), "%s/par.db", tmp);
|
||||
snprintf(db_fresh, sizeof(db_fresh), "%s/fresh.db", tmp);
|
||||
snprintf(db_fresh2, sizeof(db_fresh2), "%s/fresh2.db", tmp);
|
||||
|
||||
cbm_setenv("CBM_INDEX_SINGLE_THREAD", "1", 1);
|
||||
AxiosWrapperObs seq = observe_axios_wrapper(tmp, db_seq);
|
||||
cbm_unsetenv("CBM_INDEX_SINGLE_THREAD");
|
||||
cbm_setenv("CBM_WORKERS", "4", 1);
|
||||
AxiosWrapperObs par = observe_axios_wrapper(tmp, db_par);
|
||||
|
||||
/* Only the wrapper changes; the importing caller is untouched. */
|
||||
write_temp_file(tmp, "src/client.ts",
|
||||
"import axios from 'axios';\n"
|
||||
"const api = axios.create({ baseURL: '/v3' });\n"
|
||||
"export default api;\n");
|
||||
AxiosWrapperObs incr = observe_axios_wrapper(tmp, db_par);
|
||||
AxiosWrapperObs fresh = observe_axios_wrapper(tmp, db_fresh);
|
||||
/* Then only the caller changes; the (unchanged) wrapper's base must
|
||||
* still be found when the caller alone is re-resolved. */
|
||||
write_temp_file(
|
||||
tmp, "src/orders.ts",
|
||||
"import api from './client';\n"
|
||||
"import axios from 'axios';\n"
|
||||
"export function loadWrapperOrders(): unknown { return api.get('/invoices'); }\n"
|
||||
"export function loadDirectOrders(): unknown { return axios.get('/direct'); }\n");
|
||||
AxiosWrapperObs incr2 = observe_axios_wrapper(tmp, db_par);
|
||||
AxiosWrapperObs fresh2 = observe_axios_wrapper(tmp, db_fresh2);
|
||||
|
||||
if (saved_workers) {
|
||||
cbm_setenv("CBM_WORKERS", saved_workers, 1);
|
||||
free(saved_workers);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_WORKERS");
|
||||
}
|
||||
if (saved_single) {
|
||||
cbm_setenv("CBM_INDEX_SINGLE_THREAD", saved_single, 1);
|
||||
free(saved_single);
|
||||
} else {
|
||||
cbm_unsetenv("CBM_INDEX_SINGLE_THREAD");
|
||||
}
|
||||
th_rmtree(tmp);
|
||||
|
||||
ASSERT_EQ(seq.run_rc, 0);
|
||||
ASSERT_EQ(par.run_rc, 0);
|
||||
/* Controls: direct axios and an Express registration are unchanged, and
|
||||
* a look-alike factory never becomes a client. */
|
||||
ASSERT_EQ(seq.direct_http, 1);
|
||||
ASSERT_EQ(seq.provider_handles, 1);
|
||||
ASSERT_EQ(seq.fake_http, 0);
|
||||
|
||||
/* The issue: default-imported wrapper composes base + path. */
|
||||
ASSERT_EQ(seq.wrapper_http, 1);
|
||||
ASSERT_EQ(seq.wrapper_raw_http, 0);
|
||||
ASSERT_EQ(seq.wrapper_route_reg, 0);
|
||||
ASSERT_EQ(seq.named_http, 1);
|
||||
ASSERT_EQ(seq.local_http, 1);
|
||||
ASSERT_EQ(seq.local_raw_http, 0);
|
||||
ASSERT_EQ(seq.dynamic_http, 1);
|
||||
ASSERT_EQ(seq.dynamic_route_reg, 0);
|
||||
|
||||
/* Sequential and parallel resolvers agree edge for edge. */
|
||||
ASSERT_MEM_EQ(&seq, &par, sizeof(seq));
|
||||
|
||||
/* Reindex after the base moved converges with a fresh index. */
|
||||
ASSERT_EQ(fresh.run_rc, 0);
|
||||
ASSERT_EQ(fresh.wrapper_http_v3, 1);
|
||||
ASSERT_EQ(fresh.wrapper_http, 0);
|
||||
ASSERT_EQ(incr.run_rc, 0);
|
||||
ASSERT_MEM_EQ(&incr, &fresh, sizeof(fresh));
|
||||
ASSERT_EQ(fresh2.run_rc, 0);
|
||||
ASSERT_EQ(fresh2.wrapper_http_v3_inv, 1);
|
||||
ASSERT_EQ(fresh2.wrapper_http_v3, 0);
|
||||
ASSERT_MEM_EQ(&incr2, &fresh2, sizeof(fresh2));
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(pipeline_tsjs_receiver_suppresses_weak_method_edge) {
|
||||
char tmp[256];
|
||||
snprintf(tmp, sizeof(tmp), "/tmp/cbm_tsjs_recv_XXXXXX");
|
||||
@@ -15115,6 +15319,7 @@ SUITE(pipeline) {
|
||||
RUN_TEST(pipeline_incremental_parallel_result_cache_alloc_failure_preserves_db_and_retries);
|
||||
#endif
|
||||
RUN_TEST(pipeline_tsjs_receiver_suppresses_weak_method_edge);
|
||||
RUN_TEST(pipeline_axios_wrapper_baseurl_composes_http_calls_issue1916);
|
||||
RUN_TEST(pipeline_python_receiver_suppresses_weak_method_edge);
|
||||
RUN_TEST(pipeline_python_receiver_keeps_specific_unique_name_member_call);
|
||||
RUN_TEST(pipeline_html_embedded_member_call_stays_unbound);
|
||||
|
||||
Reference in new issue
Block a user