Merge pull request #2066 from kavish-19/fix-grep-record-alignment

test(mcp): over-long grep records never invent matches (#2011)
This commit is contained in:
Martin Vogel
2026-09-06 15:49:07 +02:00
committed by GitHub
+68
View File
@@ -10237,6 +10237,73 @@ TEST(search_code_path_filter_prefilter_keeps_matches) {
PASS();
}
/* #2011: a grep record longer than the read buffer used to be split across two
* fgets calls, and the continuation was parsed as a fresh file:line:content
* record — inventing a file path out of matched content and a line number of 0.
* A single >2 KiB line containing colons reproduces it: the repository holds
* two matches, and the split used to report three. */
TEST(search_code_long_line_does_not_invent_matches) {
char tmp[512];
snprintf(tmp, sizeof(tmp), "/tmp/cbm_srch_longline_XXXXXX");
ASSERT_TRUE(cbm_mkdtemp(tmp) != NULL);
char big_path[768], normal_path[768];
snprintf(big_path, sizeof(big_path), "%s/big.js", tmp);
snprintf(normal_path, sizeof(normal_path), "%s/normal.js", tmp);
/* One line well over the 2 KiB read buffer, full of colons, with the
* needle at the very end so the match lands past the split point. */
FILE *fp = fopen(big_path, "w");
ASSERT_NOT_NULL(fp);
fprintf(fp, "var CFG=({");
for (int i = 0; i < 260; i++) {
fprintf(fp, "k%d:\"v%d\",", i, i);
}
fprintf(fp, "NEEDLEmarker:1});\n");
fclose(fp);
fp = fopen(normal_path, "w");
ASSERT_NOT_NULL(fp);
fprintf(fp, "const NEEDLEmarker = 42;\n");
fclose(fp);
cbm_mcp_server_t *srv = cbm_mcp_server_new(NULL);
ASSERT_NOT_NULL(srv);
const char *proj = "longline-search";
cbm_mcp_server_set_project(srv, proj);
cbm_store_upsert_project(cbm_mcp_server_store(srv), proj, tmp);
char *resp = cbm_mcp_server_handle(
srv, "{\"jsonrpc\":\"2.0\",\"id\":96,\"method\":\"tools/call\","
"\"params\":{\"name\":\"search_code\","
"\"arguments\":{\"pattern\":\"NEEDLEmarker\",\"project\":\"longline-search\"}}}");
ASSERT_NOT_NULL(resp);
char *inner = extract_text_content(resp);
ASSERT_NOT_NULL(inner);
/* Exactly the two real matches — the split must not produce a third. */
int grep_matches = -1;
const char *g = strstr(inner, "\"total_grep_matches\":");
if (g) {
sscanf(g, "\"total_grep_matches\":%d", &grep_matches);
} else if ((g = strstr(inner, "total_grep_matches: ")) != NULL) {
sscanf(g, "total_grep_matches: %d", &grep_matches);
}
ASSERT_EQ(grep_matches, 2);
/* Every reported line number belongs to a real line: the fabricated row
* carried line 0, which no grep -n record can produce. */
ASSERT_TRUE(strstr(inner, "\"line\":0") == NULL);
free(inner);
free(resp);
cbm_mcp_server_free(srv);
unlink(big_path);
unlink(normal_path);
rmdir(tmp);
PASS();
}
/* PR #756 (distilled): path_filter matching ZERO indexed files. With the
* prefilter the scoped filelist has 0 records, and handle_search_code now
* skips the grep subprocess entirely (xargs on an empty filelist is
@@ -20044,6 +20111,7 @@ SUITE(mcp) {
RUN_TEST(search_code_scoped_path_with_cjk_root_issue903);
#endif
RUN_TEST(search_code_path_filter_prefilter_keeps_matches);
RUN_TEST(search_code_long_line_does_not_invent_matches);
RUN_TEST(search_code_path_filter_matches_nothing);
RUN_TEST(search_code_file_pattern_prefilter_boundaries);
RUN_TEST(search_code_windows_scope_prefilter_removes_pipeline_filter);