Files
codebase-memory-mcp/tests/test_test_impact_source.c
Martin Vogel dd598ef426 fix(test-impact): a changed code line is never a comment line
Seeding drops a hunk whose lines are all comments. The check took any line
starting with `*` for a block-comment line and any line ending in a block
comment's close for a comment, so a dereference (`*out = f(a, b, true);`)
and code with a trailing comment counted as comments and their edits seeded
nothing.

On the oracle, PR #2158 changed two `*out_* = extract_route_path_from_args(...)`
lines in scan_route_annotations. Both hunks were dropped, the function was
never a seed, and its coverage id never reached the changed set. The six
artifact_reconcile_* tests that execute it, recorded as complete coverage rows
of the merge-base map, were missed (6 per job, two jobs: the only misses
among 191 jobs). With the check fixed, all six are selected for COVERAGE
again; until now the new graph walk had reached them as STATIC only by
chance.

A line now counts as a comment only when it is nothing but comment: blank,
`//`, a block opened with nothing after its close, `*` followed by
whitespace or the end, a lone close, or the closing line of a block opened
earlier. `*name`, `**`, `*(` and code before a trailing comment are code.
When unsure the check says code, which only seeds more.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-06 18:07:16 +02:00

364 lines
16 KiB
C

/*
* test_test_impact_source.c — text facts about a snapshot's C-family sources
* (src/mcp/test_impact_source.c).
*
* The line patterns are a hand port of the regular expressions of the
* measured reference (replay4.py). The expected values in the table below are
* what those expressions return for each line (computed with Python's `re`),
* quirks included: a port that disagrees seeds differently from what was
* measured.
*/
#include "test_framework.h"
#include "mcp/test_impact_source.h"
#include <stdbool.h>
#include <string.h>
typedef struct {
const char *line;
bool comment;
bool pp;
const char *define_name;
const char *typedef_name;
const char *tag_name;
const char *extern_name;
const char *proto_name;
const char *include_path;
} tis_line_t;
static const tis_line_t tis_lines[] = {
{"", true, false, "", "", "", "", "", ""},
{" ", true, false, "", "", "", "", "", ""},
{"// x", true, false, "", "", "", "", "", ""},
{" /* x", true, false, "", "", "", "", "", ""},
{" * x", true, false, "", "", "", "", "", ""},
/* Code with a trailing comment is code (the reference called it a comment). */
{"int x; /* c */", false, false, "", "", "", "", "", ""},
{"int x;", false, false, "", "", "", "", "", ""},
{"x */ ", true, false, "", "", "", "", "", ""},
{"*/", true, false, "", "", "", "", "", ""},
{"a*/b", false, false, "", "", "", "", "", ""},
{"\t\r", true, false, "", "", "", "", "", ""},
{"#if X", false, true, "", "", "", "", "", ""},
{"#ifdef X", false, true, "", "", "", "", "", ""},
{" # ifndef X", false, true, "", "", "", "", "", ""},
{"#elif", false, true, "", "", "", "", "", ""},
{"#else", false, true, "", "", "", "", "", ""},
{"#endif", false, true, "", "", "", "", "", ""},
{"#undef X", false, true, "", "", "", "", "", ""},
{"#include <x>", false, true, "", "", "", "", "", ""},
{"#include\"x\"", false, true, "", "", "", "", "", ""},
{"#define X", false, false, "X", "", "", "", "", ""},
{"#iffy", false, false, "", "", "", "", "", ""},
{"#if(x)", false, true, "", "", "", "", "", ""},
{"#pragma once", false, false, "", "", "", "", "", ""},
{"#includes", false, false, "", "", "", "", "", ""},
{"#define X 1", false, false, "X", "", "", "", "", ""},
{" # define F(a, b) a##b", false, false, "F", "", "", "", "", ""},
{"#define", false, false, "", "", "", "", "", ""},
{"#definex", false, false, "", "", "", "", "", ""},
{"#define(X)", false, false, "", "", "", "", "", ""},
{"#define 1X", false, false, "", "", "", "", "", ""},
{"#define\tX", false, false, "X", "", "", "", "", ""},
{"#define F(x) f(x)", false, false, "F", "", "", "", "", ""},
{"typedef int myint;", false, false, "", "myint", "", "", "", ""},
{"typedef struct foo bar;", false, false, "", "bar", "", "", "", ""},
{"typedef int arr[3][4];", false, false, "", "", "", "", "", ""},
{"typedef int arr[3];", false, false, "", "arr", "", "", "", ""},
{"typedef void (*fn)(int);", false, false, "", "", "", "", "void", ""},
{"x typedef y;", false, false, "", "y", "", "", "", ""},
{"typedefs x;", false, false, "", "", "", "", "", ""},
{"typedef int a, b;", false, false, "", "b", "", "", "", ""},
{"int typedef_x;", false, false, "", "", "", "", "", ""},
{"typedef int x ;", false, false, "", "x", "", "", "", ""},
{"typedef struct { int a; } s_t;", false, false, "", "a", "", "", "", ""},
{"typedef unsigned long ulong_t [ 8 ] ;", false, false, "", "ulong_t", "", "", "", ""},
{"struct foo {", false, false, "", "", "foo", "", "", ""},
{"typedef struct foo {", false, false, "", "", "foo", "", "", ""},
{" union u{", false, false, "", "", "u", "", "", ""},
{"enum e {", false, false, "", "", "e", "", "", ""},
{"struct foo;", false, false, "", "", "", "", "", ""},
{"structfoo {", false, false, "", "", "", "", "", ""},
{"struct {", false, false, "", "", "", "", "", ""},
{"typedef enum color {", false, false, "", "", "color", "", "", ""},
{"extern int x;", false, false, "", "", "", "x", "", ""},
{"extern const char *names[];", false, false, "", "", "", "names", "", ""},
{"extern int f(void);", false, false, "", "", "", "", "f", ""},
{"extern int a, b;", false, false, "", "", "", "b", "", ""},
{" extern T x [N] ;", false, false, "", "", "", "x", "", ""},
{"externs int x;", false, false, "", "", "", "", "", ""},
{"extern \"C\" {", false, false, "", "", "", "", "", ""},
{"int foo(void)", false, false, "", "", "", "", "foo", ""},
{"foo(x)", false, false, "", "", "", "", "", ""},
{"static void foo (int a)", false, false, "", "", "", "", "foo", ""},
{" int foo(void)", false, false, "", "", "", "", "", ""},
{"int *bar(void)", false, false, "", "", "", "", "bar", ""},
{"a.b(c)", false, false, "", "", "", "", "", ""},
{"int foo\t(x)", false, false, "", "", "", "", "foo", ""},
{"return foo(x);", false, false, "", "", "", "", "foo", ""},
{"static inline T *g (void) {", false, false, "", "", "", "", "g", ""},
{"#include \"a/b.h\"", false, true, "", "", "", "", "", "a/b.h"},
{" # include \"x.h\"", false, true, "", "", "", "", "", "x.h"},
{"#include <x.h>", false, true, "", "", "", "", "", ""},
{"#include\"\"", false, true, "", "", "", "", "", ""},
{"#include \"../up/y.h\" // c", false, true, "", "", "", "", "", "../up/y.h"},
};
static bool tis_same(const char *line, bool matched, cbm_ti_span_t span, const char *expected) {
if (!*expected) {
return !matched;
}
return matched && span.len == strlen(expected) &&
memcmp(line + span.start, expected, span.len) == 0;
}
/* Every pattern gives on every line what the reference expression gives. */
TEST(test_impact_source_line_patterns_match_the_reference) {
for (size_t i = 0; i < sizeof(tis_lines) / sizeof(tis_lines[0]); i++) {
const tis_line_t *c = &tis_lines[i];
const char *s = c->line;
size_t n = strlen(s);
cbm_ti_span_t span = {0};
size_t body = 0;
bool ok = cbm_ti_line_is_comment(s, n) == c->comment &&
cbm_ti_line_is_pp_condition(s, n) == c->pp &&
tis_same(s, cbm_ti_match_define(s, n, &span, &body), span, c->define_name) &&
tis_same(s, cbm_ti_match_typedef(s, n, &span), span, c->typedef_name) &&
tis_same(s, cbm_ti_match_tag_declaration(s, n, &span), span, c->tag_name) &&
tis_same(s, cbm_ti_match_extern(s, n, &span), span, c->extern_name) &&
tis_same(s, cbm_ti_match_prototype(s, n, &span), span, c->proto_name) &&
tis_same(s, cbm_ti_match_include(s, n, &span), span, c->include_path);
if (!ok) {
printf(" line %zu: \"%s\"\n", i, s);
}
ASSERT_TRUE(ok);
}
PASS();
}
/* Only a line that is nothing but comment counts as one: a change to any
* other line is a change to code. A dereference at the start of a line read
* as a block-comment line once and dropped a real edit's seed. */
TEST(test_impact_source_only_whole_comment_lines_are_comments) {
static const struct {
const char *line;
bool comment;
} cases[] = {
{" *out_jax_path = extract_route_path_from_args(a, args, source, true);",
false},
{"*out = 1;", false},
{" **pp = q;", false},
{" *(p + 1) = 0;", false},
{" x = f(); /* why */", false},
{"/* a */ x = 1;", false},
{"*/ x = 1;", false},
{" * a block-comment line", true},
{" *", true},
{" */", true},
{"\t*\tindented */", true},
{"/* whole */", true},
{"/** doc", true},
{" end of the comment */", true},
{"// line", true},
{"", true},
};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
if (cbm_ti_line_is_comment(cases[i].line, strlen(cases[i].line)) != cases[i].comment) {
printf(" comment-line case %zu: \"%s\" expected %d\n", i, cases[i].line,
cases[i].comment);
FAIL("a line was classified wrongly");
}
}
PASS();
}
/* The declaration of a changed header line tries define, typedef, tag,
* extern, prototype in that order. */
TEST(test_impact_source_declaration_tries_the_patterns_in_order) {
static const struct {
const char *line;
const char *name;
} cases[] = {{"#define F(x) f(x)", "F"},
{"typedef void (*fn)(int);", "void"},
{"extern int f(void);", "f"},
{"typedef struct foo {", "foo"},
{"int x;", ""}};
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
cbm_ti_span_t span = {0};
const char *s = cases[i].line;
ASSERT_TRUE(
tis_same(s, cbm_ti_match_declaration(s, strlen(s), &span), span, cases[i].name));
}
PASS();
}
static cbm_ti_source_t *tis_build(const char *const *files, int count) {
cbm_ti_source_t *s = cbm_ti_source_new();
if (!s) {
return NULL;
}
for (int i = 0; i < count; i += 2) {
if (!cbm_ti_source_add(s, files[i], files[i + 1], strlen(files[i + 1]))) {
cbm_ti_source_free(s);
return NULL;
}
}
if (!cbm_ti_source_finish(s)) {
cbm_ti_source_free(s);
return NULL;
}
return s;
}
static bool tis_has(const char *const *list, int count, const char *word) {
for (int i = 0; i < count; i++) {
if (strcmp(list[i], word) == 0) {
return true;
}
}
return false;
}
/* A header's macros carry the identifiers of their body (keywords and the
* macro's own name removed, continuation lines included) and the operands of
* `##`, as the reference collects them. */
TEST(test_impact_source_macros_carry_body_names_and_pastes) {
const char *files[] = {"inc/m.h",
"#define CASE(tag_) \\\n"
" static int CASE_##tag_ = helper(x2y, 0x1F); \\\n"
" int tag_ ## _end;\n"
"#define PLAIN return NULL\n",
"src/a.c", "#define NOT_A_HEADER helper\n"};
cbm_ti_source_t *s = tis_build(files, 4);
ASSERT_NOT_NULL(s);
int count = 0;
const cbm_ti_macro_t *macros = cbm_ti_source_macros(s, &count);
ASSERT_EQ(count, 2);
ASSERT_STR_EQ(macros[0].name, "CASE");
/* `tag_` is the parameter: the reference keeps it, as it keeps `x1F` */
const char *want[] = {"CASE_", "_end", "helper", "tag_", "x1F", "x2y"};
ASSERT_EQ(macros[0].token_count, 6);
for (int i = 0; i < 6; i++) {
ASSERT_STR_EQ(macros[0].tokens[i], want[i]);
}
ASSERT_EQ(macros[0].paste_prefix_count, 2);
ASSERT_TRUE(tis_has(macros[0].paste_prefixes, 2, "CASE_"));
ASSERT_TRUE(tis_has(macros[0].paste_prefixes, 2, "tag_"));
ASSERT_EQ(macros[0].paste_suffix_count, 0);
ASSERT_STR_EQ(macros[1].name, "PLAIN");
ASSERT_EQ(macros[1].token_count, 0);
cbm_ti_source_free(s);
PASS();
}
/* `x ## y ## z`: the reference reads x and y as prefixes and never z (the
* second `##` was taken by y); `## q` alone is a suffix. */
TEST(test_impact_source_paste_scan_follows_the_reference) {
const char *files[] = {"p.h", "#define A x ## y ## z\n#define B ## q\n"};
cbm_ti_source_t *s = tis_build(files, 2);
ASSERT_NOT_NULL(s);
int count = 0;
const cbm_ti_macro_t *macros = cbm_ti_source_macros(s, &count);
ASSERT_EQ(count, 2);
ASSERT_EQ(macros[0].paste_prefix_count, 2);
ASSERT_TRUE(tis_has(macros[0].paste_prefixes, 2, "x"));
ASSERT_TRUE(tis_has(macros[0].paste_prefixes, 2, "y"));
ASSERT_EQ(macros[0].paste_suffix_count, 0);
ASSERT_EQ(macros[1].paste_prefix_count, 0);
ASSERT_EQ(macros[1].paste_suffix_count, 1);
ASSERT_STR_EQ(macros[1].paste_suffixes[0], "q");
cbm_ti_source_free(s);
PASS();
}
/* An include names the file it resolves to from the including directory
* when that file exists, otherwise every file whose path ends with it
* (leading `.` and `/` removed). The closure follows includers transitively. */
TEST(test_impact_source_include_closure_follows_the_reference) {
const char *files[] = {"src/core/x.h", "int x;\n",
"src/core/y.h", "#include \"x.h\"\n",
"src/app/main.c", "#include \"core/y.h\"\n",
"src/app/other.c", "#include \"../core/x.h\"\n",
"lib/x.h", "int other_x;\n",
"tests/t.c", "#include \"../../x.h\"\n",
"tests/u.c", "#include <x.h>\n"};
cbm_ti_source_t *s = tis_build(files, 14);
ASSERT_NOT_NULL(s);
int n = cbm_ti_source_file_count(s);
ASSERT_EQ(n, 7);
bool marked[7] = {false};
marked[cbm_ti_source_find(s, "src/core/x.h")] = true;
cbm_ti_source_include_closure(s, marked);
/* y.h resolves x.h beside it; main.c reaches y.h through the suffix
* "core/y.h"; other.c through ../core/x.h; t.c's "../../x.h" does not
* exist relative to tests/, so its key "x.h" names BOTH x.h files. */
ASSERT_TRUE(marked[cbm_ti_source_find(s, "src/core/y.h")]);
ASSERT_TRUE(marked[cbm_ti_source_find(s, "src/app/main.c")]);
ASSERT_TRUE(marked[cbm_ti_source_find(s, "src/app/other.c")]);
ASSERT_TRUE(marked[cbm_ti_source_find(s, "tests/t.c")]);
ASSERT_FALSE(marked[cbm_ti_source_find(s, "lib/x.h")]);
ASSERT_FALSE(marked[cbm_ti_source_find(s, "tests/u.c")]);
cbm_ti_source_free(s);
PASS();
}
/* One place per line; comment-led lines and keywords hold none; identifiers
* in strings and trailing comments count (an over-approximation that only
* adds seeds); `0x1F` holds `x1F` as the reference's token pattern reads it. */
TEST(test_impact_source_occurrences_follow_the_reference) {
const char *files[] = {"b.c",
"int f(void) { return f2(f); }\n"
"// f in a comment line\n"
" * f in a block comment line\n"
"const char *s = \"f\"; /* f */\n"
"int y = 0x1F;\n",
"a.c", "void g(void) { f(); }\n"};
cbm_ti_source_t *s = tis_build(files, 4);
ASSERT_NOT_NULL(s);
ASSERT_TRUE(cbm_ti_source_build_occurrences(s));
int count = 0;
const cbm_ti_place_t *places = cbm_ti_source_occurrences(s, "f", 1, &count);
ASSERT_EQ(count, 3);
ASSERT_STR_EQ(cbm_ti_source_path(s, places[0].file), "a.c");
ASSERT_EQ(places[0].line, 1);
ASSERT_STR_EQ(cbm_ti_source_path(s, places[1].file), "b.c");
ASSERT_EQ(places[1].line, 1);
ASSERT_EQ(places[2].line, 4);
(void)cbm_ti_source_occurrences(s, "x1F", 3, &count);
ASSERT_EQ(count, 1);
(void)cbm_ti_source_occurrences(s, "int", 3, &count);
ASSERT_EQ(count, 0);
cbm_ti_source_free(s);
PASS();
}
/* Lines split as `text.split('\n')`: a final newline leaves an empty line. */
TEST(test_impact_source_lines_split_like_the_reference) {
const char *files[] = {"x.c", "a\r\nb\n"};
cbm_ti_source_t *s = tis_build(files, 2);
ASSERT_NOT_NULL(s);
ASSERT_EQ(cbm_ti_source_line_count(s, 0), 3);
size_t len = 0;
const char *line = cbm_ti_source_line(s, 0, 1, &len);
ASSERT_EQ(len, 2);
ASSERT_TRUE(memcmp(line, "a\r", 2) == 0);
(void)cbm_ti_source_line(s, 0, 3, &len);
ASSERT_EQ(len, 0);
ASSERT_NULL(cbm_ti_source_line(s, 0, 4, &len));
ASSERT_FALSE(cbm_ti_source_add(s, "late.c", "", 0));
cbm_ti_source_free(s);
PASS();
}
SUITE(test_impact_source) {
RUN_TEST(test_impact_source_line_patterns_match_the_reference);
RUN_TEST(test_impact_source_only_whole_comment_lines_are_comments);
RUN_TEST(test_impact_source_declaration_tries_the_patterns_in_order);
RUN_TEST(test_impact_source_macros_carry_body_names_and_pastes);
RUN_TEST(test_impact_source_paste_scan_follows_the_reference);
RUN_TEST(test_impact_source_include_closure_follows_the_reference);
RUN_TEST(test_impact_source_occurrences_follow_the_reference);
RUN_TEST(test_impact_source_lines_split_like_the_reference);
}