Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions internal/cbm/extract_defs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3196,6 +3196,54 @@ static char *go_receiver_type_name(CBMArena *a, TSNode recv, const char *source)
return NULL;
}

/* C++/CUDA: true when name is a GoogleTest test-definition macro whose
* invocations parse as function definitions with bare-identifier parameters.
* Multiple such macros in one file all share the extracted name (e.g. "TEST"),
* causing qualified-name collisions and node loss (#1266). */
static bool is_cpp_test_macro(const char *name) {
return strcmp(name, "TEST") == 0 || strcmp(name, "TEST_F") == 0 ||
strcmp(name, "TEST_P") == 0 || strcmp(name, "TYPED_TEST") == 0 ||
strcmp(name, "TYPED_TEST_P") == 0;
}

/* Compose a unique name from a GoogleTest-style macro invocation by appending
* the macro arguments: TEST(Suite, Case) -> "TEST_Suite_Case".
* Returns an arena-allocated string, or NULL when the parameters cannot be
* resolved (caller keeps the original name in that case). */
static char *resolve_cpp_test_macro_name(CBMArena *a, const char *macro, TSNode node,
const char *source) {
TSNode params = ts_node_child_by_field_name(node, TS_FIELD("parameters"));
if (ts_node_is_null(params)) {
params = find_c_params(node);
}
if (ts_node_is_null(params)) {
return NULL;
}

const char *args[2] = {NULL, NULL};
int count = 0;
uint32_t nc = ts_node_named_child_count(params);
for (uint32_t i = 0; i < nc && count < 2; i++) {
TSNode child = ts_node_named_child(params, i);
if (ts_node_is_null(child)) {
continue;
}
TSNode type_node = ts_node_child_by_field_name(child, TS_FIELD("type"));
char *text = cbm_node_text(a, ts_node_is_null(type_node) ? child : type_node, source);
if (text && text[0]) {
args[count++] = text;
}
}

if (count == 2) {
return cbm_arena_sprintf(a, "%s_%s_%s", macro, args[0], args[1]);
}
if (count == 1) {
return cbm_arena_sprintf(a, "%s_%s", macro, args[0]);
}
return NULL;
}

static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec *spec) {
CBMArena *a = ctx->arena;

Expand All @@ -3217,6 +3265,20 @@ static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
return;
}

/* C++/CUDA: GoogleTest macros (TEST, TEST_F, TEST_P, ...) parse as
* function definitions whose name resolves to the bare macro identifier.
* Multiple test cases per file collide on qualified name; derive a unique
* name from the macro arguments so each gets its own graph node (#1266). */
bool is_gtest = false;
if ((ctx->language == CBM_LANG_CPP || ctx->language == CBM_LANG_CUDA) &&
is_cpp_test_macro(name)) {
char *gtest_name = resolve_cpp_test_macro_name(a, name, node, ctx->source);
if (gtest_name) {
name = gtest_name;
is_gtest = true;
}
}

TSNode func_node = unwrap_template_inner(node, ctx->language);

CBMDefinition def;
Expand Down Expand Up @@ -3344,6 +3406,11 @@ static void extract_func_def(CBMExtractCtx *ctx, TSNode node, const CBMLangSpec
def.is_test = rust_def_is_test(def.decorators);
}

// C++/CUDA: GoogleTest macros are test functions (#1266).
if (is_gtest) {
def.is_test = true;
}

// Docstring
def.docstring = extract_docstring(a, node, ctx->source, ctx->language);

Expand Down
37 changes: 37 additions & 0 deletions tests/test_extraction.c
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,41 @@ TEST(cpp_function) {
PASS();
}

/* #1266: GoogleTest TEST() macros with the same name collapse into a single
* node when multiple tests share a file. Each must mint a distinct Function
* node whose name encodes the suite and case arguments. */
TEST(cpp_gtest_same_name_collision_issue1266) {
CBMFileResult *r = extract(
"namespace demo { int assembleWidget(int s) { return s * 2; } }\n"
"TEST(WidgetSuite, DoublesSmallSize) { demo::assembleWidget(1); }\n"
"TEST(WidgetSuite, DoublesZero) { demo::assembleWidget(0); }\n"
"TEST(WidgetSuite, DoublesLargeSize) {\n"
" demo::assembleWidget(1000);\n"
"}\n",
CBM_LANG_CPP, "t", "direct_test.cpp");
ASSERT_NOT_NULL(r);
ASSERT(has_def(r, "Function", "TEST_WidgetSuite_DoublesSmallSize"));
ASSERT(has_def(r, "Function", "TEST_WidgetSuite_DoublesZero"));
ASSERT(has_def(r, "Function", "TEST_WidgetSuite_DoublesLargeSize"));
ASSERT(!has_def(r, "Function", "TEST"));
cbm_free_result(r);
PASS();
}

/* #1266: TEST_F fixture macro also produces unique names. */
TEST(cpp_gtest_f_unique_name_issue1266) {
CBMFileResult *r = extract(
"TEST_F(MyFixture, FirstTest) { doStuff(); }\n"
"TEST_F(MyFixture, SecondTest) { doOtherStuff(); }\n",
CBM_LANG_CPP, "t", "fixture_test.cpp");
ASSERT_NOT_NULL(r);
ASSERT(has_def(r, "Function", "TEST_F_MyFixture_FirstTest"));
ASSERT(has_def(r, "Function", "TEST_F_MyFixture_SecondTest"));
ASSERT(!has_def(r, "Function", "TEST_F"));
cbm_free_result(r);
PASS();
}

/* --- C++ out-of-line method definitions (#428) ---
* A .cpp defining methods of a class declared elsewhere (not in this TU).
* Pre-fix these were recorded as free Functions (label "Function", no
Expand Down Expand Up @@ -4816,6 +4851,8 @@ SUITE(extraction) {
RUN_TEST(rust_enum);
RUN_TEST(zig_struct);
RUN_TEST(cpp_function);
RUN_TEST(cpp_gtest_same_name_collision_issue1266);
RUN_TEST(cpp_gtest_f_unique_name_issue1266);
RUN_TEST(cpp_out_of_line_method_issue428);
RUN_TEST(cobol_paragraph);
RUN_TEST(verilog_module);
Expand Down
Loading