mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
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:
1 parent
3457b21b0a
commit
ad7899399b
9 files changed
+327
-13
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user