fix(routes): read Go 1.22 ServeMux method and host patterns

Since Go 1.22 a net/http ServeMux pattern may name a method and a host
before the path: mux.HandleFunc("POST /v1/payments", h),
"GET /v1/payments/{id}", "api.example.com/v1/x". Every route
registration gate wanted the first argument to open with a slash (the
extractor's handler capture, find_route_path_in_args in the parallel
pass, and the three gates in the sequential pass), so such a
registration minted no Route and no HANDLES edge, while the same file
written with "/v1/payments" had both.

cbm_service_pattern_mux_split reads the "[METHOD ][HOST]/[PATH]"
grammar. For Go files only, the path of such a pattern now registers
the route, the Route takes the pattern's method when the callee names
none (Handle/HandleFunc are ANY), and a host is dropped from the path.
Both passes share cbm_pipeline_mux_route_path and
cbm_pipeline_route_reg_method so their Routes stay identical. Other
languages and plain "/path" patterns take the unchanged path.

Fixes #2537

Signed-off-by: Gaurav Dubey <gauravdubey0107@gmail.com>
This commit is contained in:
Gaurav Dubey committed 2026-10-21 13:23:06 +00:00
1 parent 3457b21b0a
commit ad7899399b
9 files changed
+327 -13

No files matched your search

+7 -2
View File
@@ -4553,8 +4553,13 @@ CBMInvocationDescriptor handle_calls(CBMExtractCtx *ctx, TSNode node, const CBML
call.first_string_arg = route_path_with_group_prefix(ctx, node, call.callee_name,
call.first_string_arg);
/* A slashless registration's string action would only be
* guessed at (#1146): mint its Route without a handler. */
if (call.first_string_arg && call.first_string_arg[0] == '/' &&
* guessed at (#1146): mint its Route without a handler. A Go
* ServeMux pattern names its method or host before the slash
* ("POST /v1/x", #2537) and keeps its handler. */
if (call.first_string_arg &&
(call.first_string_arg[0] == '/' ||
(ctx->language == CBM_LANG_GO &&
cbm_service_pattern_mux_split(call.first_string_arg, NULL, 0) != NULL)) &&
!(laravel_uri && php_route_action_is_string(node))) {
call.second_arg_name = extract_handler_arg(ctx, args);
}
+44
View File
@@ -1183,6 +1183,50 @@ const char *cbm_service_pattern_route_method(const char *callee_name) {
return NULL;
}
static bool mux_is_blank(char ch) {
return ch == ' ' || ch == '\t';
}
const char *cbm_service_pattern_mux_split(const char *pattern, char *method, size_t method_sz) {
if (method && method_sz > 0) {
method[0] = '\0';
}
if (!pattern || pattern[0] == '/') {
return NULL;
}
/* METHOD: Go cuts it at the first space or tab and trims the rest. */
const char *rest = pattern;
size_t method_len = 0;
while (pattern[method_len] >= 'A' && pattern[method_len] <= 'Z') {
method_len++;
}
if (method_len > 0 && mux_is_blank(pattern[method_len])) {
rest = pattern + method_len;
while (mux_is_blank(*rest)) {
rest++;
}
} else {
method_len = 0;
}
/* HOST: everything before the path's slash, with no blank or line break. */
const char *slash = rest;
while (*slash && *slash != '/' && !mux_is_blank(*slash) && *slash != '\n' && *slash != '\r') {
slash++;
}
if (*slash != '/' || (method_len == 0 && slash == rest)) {
return NULL;
}
if (method_len > 0 && method) {
if (method_len >= method_sz) {
method[0] = '\0';
return NULL;
}
memcpy(method, pattern, method_len);
method[method_len] = '\0';
}
return slash;
}
const char *cbm_service_pattern_broker(const char *resolved_qn) {
if (!resolved_qn) {
return NULL;
+11
View File
@@ -84,6 +84,17 @@ const char *cbm_service_pattern_http_method(const char *callee_name);
* Returns NULL if not a known route registration method. */
const char *cbm_service_pattern_route_method(const char *callee_name);
/* Go 1.22+ net/http ServeMux pattern "[METHOD ][HOST]/[PATH]" (#2537):
* "POST /v1/x", "GET api.example.com/v1/x", "api.example.com/v1/x". Returns a
* pointer to PATH's leading slash inside `pattern`, or NULL when the pattern
* names neither a method nor a host before a slash (a plain "/v1/x" is NULL
* too: there is nothing to split). METHOD is a run of uppercase letters ended
* by spaces or tabs; it is copied to `method` (NUL-terminated, "" when the
* pattern names none) when `method` is non-NULL; a method that does not fit
* `method_sz` then makes the pattern NULL. The grammar is Go's: callers gate
* on the file language. */
const char *cbm_service_pattern_mux_split(const char *pattern, char *method, size_t method_sz);
/* Get the broker name for an async QN (e.g., "pubsub" from a Pub/Sub QN).
* Returns NULL if not an async pattern. */
const char *cbm_service_pattern_broker(const char *resolved_qn);
+23 -7
View File
@@ -571,6 +571,21 @@ cbm_svc_kind_t cbm_pipeline_external_import_service(cbm_external_import_index_t
return CBM_SVC_NONE;
}
/* The path a route registration's first argument names: the argument itself
* when it opens with a slash, or in Go the path of a ServeMux pattern that
* names a method or host first ("POST /v1/x", #2537). NULL when it names no
* path. MUST match pass_parallel.c find_route_path_in_args. */
static const char *route_reg_path(const CBMCall *call, CBMLanguage lang) {
const char *arg = call->first_string_arg;
if (!arg) {
return NULL;
}
if (arg[0] == '/') {
return arg;
}
return cbm_pipeline_mux_route_path(arg, lang);
}
/* Handle a route registration call: create Route node + HANDLES edge.
* route_mount is the framework mount of the registering file ("/api" for a
* Laravel 11+ `withRouting(api: ...)` file, #1146) or "". */
@@ -578,10 +593,11 @@ static void handle_route_registration(cbm_pipeline_ctx_t *ctx, const CBMCall *ca
const cbm_gbuf_node_t *source_node, const char *module_qn,
const char **imp_keys, const char **imp_vals, int imp_count,
const char *route_mount, CBMLanguage lang) {
const char *method = cbm_service_pattern_route_method(call->callee_name);
char mux_method[CBM_SZ_16];
const char *method = cbm_pipeline_route_reg_method(call, lang, mux_method, sizeof(mux_method));
char mounted[CBM_SZ_256];
const char *path =
cbm_laravel_mount_route(route_mount, call->first_string_arg, mounted, sizeof(mounted));
cbm_laravel_mount_route(route_mount, route_reg_path(call, lang), mounted, sizeof(mounted));
char route_qn[CBM_ROUTE_QN_SIZE];
char cpath[CBM_SZ_256];
snprintf(route_qn, sizeof(route_qn), "__route__%s__%s", method ? method : "ANY",
@@ -815,7 +831,7 @@ static void emit_classified_edge(cbm_pipeline_ctx_t *ctx, const CBMCall *call,
bool suppress_plain_calls, const char *route_mount,
CBMLanguage lang) {
cbm_svc_kind_t svc = cbm_service_pattern_match(res->qualified_name);
if (svc == CBM_SVC_ROUTE_REG && call->first_string_arg && call->first_string_arg[0] == '/') {
if (svc == CBM_SVC_ROUTE_REG && route_reg_path(call, lang) != NULL) {
handle_route_registration(ctx, call, source, module_qn, imp_keys, imp_vals, imp_count,
route_mount, lang);
return;
@@ -1042,8 +1058,8 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
* apps. Classify by callee suffix + path-shaped first arg, exactly
* like the parallel path's callee_suffix fallback; without this the
* sequential path minted zero Route nodes for such files. */
if (cbm_service_pattern_route_method(call->callee_name) != NULL && call->first_string_arg &&
call->first_string_arg[0] == '/') {
if (cbm_service_pattern_route_method(call->callee_name) != NULL &&
route_reg_path(call, lang) != NULL) {
handle_route_registration(ctx, call, source_node, module_qn, imp_keys, imp_vals,
imp_count, route_mount, lang);
return SKIP_ONE;
@@ -1220,8 +1236,8 @@ static int resolve_single_call(cbm_pipeline_ctx_t *ctx, CBMCall *call, const CBM
* unresolved-callee fallback above mints, and nothing else. Dropping
* the whole call lost every GET registration in a mixed-language repo
* while POST (no `post` to collide with) survived. */
if (cbm_service_pattern_route_method(call->callee_name) != NULL && call->first_string_arg &&
call->first_string_arg[0] == '/') {
if (cbm_service_pattern_route_method(call->callee_name) != NULL &&
route_reg_path(call, lang) != NULL) {
handle_route_registration(ctx, call, source_node, module_qn, imp_keys, imp_vals,
imp_count, route_mount, lang);
return SKIP_ONE;
+33 -4
View File
@@ -2263,13 +2263,41 @@ static bool is_route_path_shaped(const char *val) {
return val && val[0] == '/' && !cbm_service_pattern_is_comment_text(val);
}
static const char *find_route_path_in_args(const CBMCall *call, const char **out_handler) {
const char *cbm_pipeline_mux_route_path(const char *arg, CBMLanguage lang) {
if (lang != CBM_LANG_GO || !arg || cbm_service_pattern_is_comment_text(arg)) {
return NULL;
}
return cbm_service_pattern_mux_split(arg, NULL, 0);
}
const char *cbm_pipeline_route_reg_method(const CBMCall *call, CBMLanguage lang, char *buf,
size_t buf_sz) {
const char *method = cbm_service_pattern_route_method(call->callee_name);
if (method && strcmp(method, "ANY") != 0) {
return method;
}
if (cbm_pipeline_mux_route_path(call->first_string_arg, lang) &&
cbm_service_pattern_mux_split(call->first_string_arg, buf, buf_sz) && buf[0] != '\0') {
return buf;
}
return method;
}
static const char *find_route_path_in_args(const CBMCall *call, const char **out_handler,
CBMLanguage lang) {
*out_handler = NULL;
/* 1. First string arg starting with / */
if (is_route_path_shaped(call->first_string_arg)) {
*out_handler = call->second_arg_name;
return call->first_string_arg;
}
/* 1b. Go ServeMux pattern with a method or host before the path (#2537).
* MUST match pass_calls.c route_reg_path. */
const char *mux_path = cbm_pipeline_mux_route_path(call->first_string_arg, lang);
if (mux_path) {
*out_handler = call->second_arg_name;
return mux_path;
}
/* 2. Keyword args (prefix=, path=, route=, etc.) */
const char *found = NULL;
for (int ai = 0; ai < call->arg_count && !found; ai++) {
@@ -2428,7 +2456,8 @@ static void emit_route_registration(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *sou
const cbm_registry_t *registry, const cbm_gbuf_t *main_gbuf,
const char **ik, const char **iv, int ic,
const char *route_mount, CBMLanguage lang) {
const char *method = cbm_service_pattern_route_method(call->callee_name);
char mux_method[CBM_SZ_16];
const char *method = cbm_pipeline_route_reg_method(call, lang, mux_method, sizeof(mux_method));
char mounted[CBM_SZ_256];
route_path = cbm_laravel_mount_route(route_mount, route_path, mounted, sizeof(mounted));
char rqn[CBM_ROUTE_QN_SIZE];
@@ -2813,7 +2842,7 @@ static void emit_service_edge(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *source,
if (svc == CBM_SVC_ROUTE_REG) {
const char *handler_ref = NULL;
const char *route_path = find_route_path_in_args(call, &handler_ref);
const char *route_path = find_route_path_in_args(call, &handler_ref, lang);
if (route_path) {
emit_route_registration(gbuf, source, call, route_path, handler_ref, module_qn,
registry, main_gbuf, imp_keys, imp_vals, imp_count, route_mount,
@@ -2876,7 +2905,7 @@ static void emit_xlang_refused_route(cbm_gbuf_t *gbuf, const cbm_gbuf_node_t *so
return;
}
const char *handler_ref = NULL;
const char *route_path = find_route_path_in_args(call, &handler_ref);
const char *route_path = find_route_path_in_args(call, &handler_ref, lang);
if (route_path) {
emit_route_registration(gbuf, source, call, route_path, handler_ref, module_qn, registry,
main_gbuf, imp_keys, imp_vals, imp_count, route_mount, lang);
+15
View File
@@ -787,6 +787,21 @@ bool cbm_pipeline_http_client_call_url(const cbm_gbuf_t *gbuf, const char *proje
* behind both passes' HTTP_CALLS / ASYNC_CALLS props (pass_parallel.c). */
void cbm_pipeline_close_call_props(char *props, int n, const CBMCall *call, bool with_line);
/* Go 1.22 ServeMux registrations may name a method and a host before the path
* ("POST /v1/x", "GET api.example.com/v1/x", #2537). For a Go file, the path
* inside such a route-registration argument; NULL for any other language and
* for an argument that is no such pattern (a plain "/v1/x" included). The one
* reader of that grammar behind both passes (pass_parallel.c). */
const char *cbm_pipeline_mux_route_path(const char *arg, CBMLanguage lang);
/* The method of a route registration's Route: the verb its callee names
* (`router.GET`), else the method its Go ServeMux pattern names
* (`mux.HandleFunc("POST /v1/x", h)`, #2537), else the callee's own answer
* (ANY or NULL). `buf` (CBM_SZ_16) holds a pattern method. Shared by both
* passes so their Routes stay byte-identical (pass_parallel.c). */
const char *cbm_pipeline_route_reg_method(const CBMCall *call, CBMLanguage lang, char *buf,
size_t buf_sz);
/* Per-file index of the names a file's imports bind onto EXTERNAL modules:
* local name -> the binding import. Built lazily, on the first call that
* asks, from the file's extraction imports and its IMPORTS edges; one hash
+35
View File
@@ -6207,6 +6207,40 @@ TEST(extract_go_binary_concat_url_no_literal_suffix_issue1249) {
PASS();
}
/* Issue #2537: a Go 1.22 ServeMux pattern may open with a method and/or a host
* ("POST /v1/payments", "GET api.example.com/v1/x"). The written pattern stays
* the first string argument, and its handler is captured exactly as for a
* plain "/v1/payments"; it was dropped because the pattern does not open with
* a slash. */
TEST(extract_go_servemux_method_pattern_handler_issue2537) {
CBMFileResult *r = extract("package main\n"
"import \"net/http\"\n"
"func setup(mux *http.ServeMux) {\n"
" mux.HandleFunc(\"POST /v1/payments\", createPayment)\n"
" mux.Handle(\"GET api.example.com/v1/x\", hostHandler)\n"
"}\n",
CBM_LANG_GO, "t", "routes.go");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
const CBMCall *method = find_call_by_callee(r, "mux.HandleFunc");
ASSERT_NOT_NULL(method);
ASSERT_NOT_NULL(method->first_string_arg);
ASSERT_STR_EQ(method->first_string_arg, "POST /v1/payments");
ASSERT_NOT_NULL(method->second_arg_name);
ASSERT_STR_EQ(method->second_arg_name, "createPayment");
const CBMCall *host = find_call_by_callee(r, "mux.Handle");
ASSERT_NOT_NULL(host);
ASSERT_NOT_NULL(host->first_string_arg);
ASSERT_STR_EQ(host->first_string_arg, "GET api.example.com/v1/x");
ASSERT_NOT_NULL(host->second_arg_name);
ASSERT_STR_EQ(host->second_arg_name, "hostHandler");
cbm_free_result(r);
PASS();
}
/* Issue #2291: the JS/TS twin of #1249. A BFF that builds its backend URL as
* `${process.env.BACKEND_URL}/wallet/topup` from a configurable base must
* index the literal path after the base, exactly as `BACKEND_URL + "/x"`
@@ -13442,6 +13476,7 @@ SUITE(extraction) {
RUN_TEST(extract_ts_config_object_url_issue2235);
RUN_TEST(extract_go_binary_concat_url_issue1249);
RUN_TEST(extract_go_binary_concat_url_no_literal_suffix_issue1249);
RUN_TEST(extract_go_servemux_method_pattern_handler_issue2537);
RUN_TEST(extract_ts_template_base_url_issue2291);
RUN_TEST(extract_ts_template_base_url_no_literal_path_issue2291);
RUN_TEST(extract_py_concat_const_url_issue706);
+34
View File
@@ -549,12 +549,46 @@ TEST(infrascan_data_flows_test_handlers_pair_only_within_their_file) {
PASS();
}
/* Issue #2537: the Go 1.22 ServeMux grammar "[METHOD ][HOST]/[PATH]". The path
* is found inside the written pattern; a plain path, a lowercase word, a
* pattern without a path and a method too long for the buffer are no split. */
TEST(infrascan_servemux_pattern_split_issue2537) {
char m[16];
const char *pat = "POST /v1/payments";
ASSERT_TRUE(cbm_service_pattern_mux_split(pat, m, sizeof(m)) == pat + 5);
ASSERT_STR_EQ(m, "POST");
pat = "GET \t /v1/payments/{id}";
ASSERT_TRUE(cbm_service_pattern_mux_split(pat, m, sizeof(m)) == pat + 6);
ASSERT_STR_EQ(m, "GET");
pat = "GET api.example.com/v1/x";
ASSERT_TRUE(cbm_service_pattern_mux_split(pat, m, sizeof(m)) == pat + 19);
ASSERT_STR_EQ(m, "GET");
pat = "api.example.com:8443/v1/x";
ASSERT_TRUE(cbm_service_pattern_mux_split(pat, m, sizeof(m)) == pat + 20);
ASSERT_STR_EQ(m, "");
pat = "DELETE /";
ASSERT_TRUE(cbm_service_pattern_mux_split(pat, NULL, 0) == pat + 7);
ASSERT_NULL(cbm_service_pattern_mux_split("/v1/x", m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split("get /v1/x", m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split("POST", m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split("POST ", m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split("GET host", m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split("GET a b/x", m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split("", m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split(NULL, m, sizeof(m)));
ASSERT_NULL(cbm_service_pattern_mux_split("ABCDEFGHIJKLMNOP /x", m, sizeof(m)));
ASSERT_STR_EQ(m, "");
PASS();
}
SUITE(infrascan) {
RUN_TEST(infrascan_http_route_literal_guard_rejects_filesystem_paths);
RUN_TEST(infrascan_http_route_literal_guard_rejects_document_extensions);
RUN_TEST(infrascan_client_navigation_is_js_family_only);
RUN_TEST(infrascan_service_pattern_match_rejects_ids_inside_words);
RUN_TEST(infrascan_service_pattern_match_keeps_real_library_qns);
RUN_TEST(infrascan_servemux_pattern_split_issue2537);
RUN_TEST(infrascan_route_nodes_skip_bad_http_url_paths);
RUN_TEST(infrascan_http_calls_join_matching_handler_route);
RUN_TEST(infrascan_data_flows_link_every_caller_of_a_route);
+125
View File
@@ -13376,6 +13376,130 @@ TEST(pipeline_route_prefix_bridge_is_order_free_and_root_bridges_nothing) {
PASS();
}
/* Issue #2537: since Go 1.22 a ServeMux pattern may name a method and a host
* before the path ("POST /v1/payments", "GET api.example.com/v1/x"). Such a
* registration minted no Route and no HANDLES edge, because every registration
* gate wanted the first argument to open with a slash, while the same file
* written with "/v1/payments" had both. The method now comes from the pattern
* and a host is dropped from the path, in the sequential and the parallel pass
* alike. Only Go reads that grammar: a Python registration of "GET /py" is still
* no route. */
TEST(pipeline_go_servemux_method_patterns_issue2537) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_servemux_XXXXXX");
if (!cbm_mkdtemp(tmp)) {
FAIL("tmpdir");
}
write_temp_file(tmp, "go.mod", "module example.com/srv\n\ngo 1.22\n");
write_temp_file(tmp, "main.go",
"package main\n"
"\n"
"import \"net/http\"\n"
"\n"
"func createPayment(w http.ResponseWriter, r *http.Request) {}\n"
"func getPayment(w http.ResponseWriter, r *http.Request) {}\n"
"func hostOnly(w http.ResponseWriter, r *http.Request) {}\n"
"func legacyList(w http.ResponseWriter, r *http.Request) {}\n"
"\n"
"func main() {\n"
"\tmux := http.NewServeMux()\n"
"\tmux.HandleFunc(\"POST /v1/payments\", createPayment)\n"
"\tmux.HandleFunc(\"GET /v1/payments/{id}\", getPayment)\n"
"\tmux.HandleFunc(\"api.example.com/v1/hosted\", hostOnly)\n"
"\tmux.HandleFunc(\"/v1/legacy\", legacyList)\n"
"\t_ = http.ListenAndServe(\":8080\", mux)\n"
"}\n");
write_temp_file(tmp, "web/site.py",
"from flask import Flask\n"
"app = Flask(__name__)\n"
"\n"
"def py_view():\n"
" return \"ok\"\n"
"\n"
"app.add_url_rule(\"GET /py\", \"py_view\", py_view)\n");
/* past MIN_FILES_FOR_PARALLEL, so the threaded run takes the parallel pass */
for (int i = 0; i < 52; i++) {
char name[64];
char body[128];
snprintf(name, sizeof(name), "pad/padding%d.go", i);
snprintf(body, sizeof(body), "package pad\n\nfunc Padding%d() int { return %d }\n", i, i);
write_temp_file(tmp, name, body);
}
const char *old_single = getenv("CBM_INDEX_SINGLE_THREAD");
char *saved_single = old_single ? strdup(old_single) : NULL;
const char *old_workers = getenv("CBM_WORKERS");
char *saved_workers = old_workers ? strdup(old_workers) : NULL;
int run_rc[2] = {-1, -1};
bool opened[2] = {false, false};
int post[2] = {-1, -1};
int get_id[2] = {-1, -1};
int any_post[2] = {-1, -1};
int host[2] = {-1, -1};
int legacy[2] = {-1, -1};
int py_any[2] = {-1, -1};
int py_get[2] = {-1, -1};
for (int mode = 0; mode < 2; mode++) {
if (mode == 0) {
cbm_setenv("CBM_INDEX_SINGLE_THREAD", "1", 1);
} else {
cbm_unsetenv("CBM_INDEX_SINGLE_THREAD");
cbm_setenv("CBM_WORKERS", "4", 1);
}
char db_path[512];
snprintf(db_path, sizeof(db_path), "%s/servemux_%d.db", tmp, mode);
cbm_pipeline_t *p = cbm_pipeline_new(tmp, db_path, CBM_MODE_FULL);
run_rc[mode] = p ? cbm_pipeline_run(p) : -1;
const char *project = p ? cbm_pipeline_project_name(p) : NULL;
cbm_store_t *s = cbm_store_open_path(db_path);
opened[mode] = s != NULL;
if (s && project) {
post[mode] =
handles_to_route(s, project, "createPayment", "__route__POST__/v1/payments");
get_id[mode] =
handles_to_route(s, project, "getPayment", "__route__GET__/v1/payments/{}");
any_post[mode] =
handles_to_route(s, project, "createPayment", "__route__ANY__/v1/payments");
host[mode] = handles_to_route(s, project, "hostOnly", "__route__ANY__/v1/hosted");
legacy[mode] = handles_to_route(s, project, "legacyList", "__route__ANY__/v1/legacy");
py_any[mode] = handles_to_route(s, project, "py_view", "__route__ANY__/py");
py_get[mode] = handles_to_route(s, project, "py_view", "__route__GET__/py");
}
if (s) {
cbm_store_close(s);
}
cbm_pipeline_free(p);
}
if (saved_single) {
cbm_setenv("CBM_INDEX_SINGLE_THREAD", saved_single, 1);
free(saved_single);
} else {
cbm_unsetenv("CBM_INDEX_SINGLE_THREAD");
}
if (saved_workers) {
cbm_setenv("CBM_WORKERS", saved_workers, 1);
free(saved_workers);
} else {
cbm_unsetenv("CBM_WORKERS");
}
th_rmtree(tmp);
for (int mode = 0; mode < 2; mode++) {
ASSERT_EQ(run_rc[mode], 0);
ASSERT_TRUE(opened[mode]);
/* the method is the pattern's, not ANY */
ASSERT_EQ(post[mode], 1);
ASSERT_EQ(get_id[mode], 1);
ASSERT_EQ(any_post[mode], 0);
/* a host names no method and is not part of the path */
ASSERT_EQ(host[mode], 1);
/* control: a plain path is unchanged */
ASSERT_EQ(legacy[mode], 1);
/* another language's "GET /py" is not read as a ServeMux pattern */
ASSERT_EQ(py_any[mode], 0);
ASSERT_EQ(py_get[mode], 0);
}
PASS();
}
TEST(pipeline_swift_http_call_makes_route_issue1892) {
char tmp[256];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_swifthttp_XXXXXX");
@@ -27725,6 +27849,7 @@ SUITE(pipeline) {
RUN_TEST(pipeline_swift_nested_url_makes_route_issue1892);
RUN_TEST(pipeline_swift_http_call_makes_route_issue1892);
RUN_TEST(pipeline_route_prefix_bridge_is_order_free_and_root_bridges_nothing);
RUN_TEST(pipeline_go_servemux_method_patterns_issue2537);
RUN_TEST(pipeline_native_fetch_parallel_classified_as_http_calls);
RUN_TEST(pipeline_local_fetch_shadow_not_classified_as_http);
RUN_TEST(pipeline_typed_http_call_has_no_arg_url_twin);