Skip to content
Merged
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
69 changes: 69 additions & 0 deletions tests/test_extraction.c
Original file line number Diff line number Diff line change
Expand Up @@ -3115,6 +3115,74 @@ TEST(complexity_go_method_receiver_self_recursion) {
PASS();
}

/* #876: the delegation/store bucket left open after #599 — same-named calls
* whose receiver is a CALL RESULT (_get_store().get), a module singleton
* (_default.check), or a local cursor inside a loop (cur.execute) are not
* self-recursion. All three are the reporter's exact v0.8.1 false positives;
* fixed by the receiver-aware narrowing (87091ed), guarded here so the
* bucket never regresses. */
TEST(complexity_delegation_receivers_not_recursive_issue876) {
/* Store wrapper: _get_store().get() inside get — receiver is the call
* result, a different object. */
CBMFileResult *r = extract("def _get_store():\n"
" return object()\n"
"\n"
"def get(collection, record_id):\n"
" rec = _get_store().get(collection, record_id)\n"
" return rec\n",
CBM_LANG_PYTHON, "t", "soil.py");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
const CBMDefinition *d = find_def(r, "get");
ASSERT_NOT_NULL(d);
ASSERT_FALSE(d->is_recursive); /* _get_store() result is not this fn */
ASSERT_FALSE(d->unguarded_recursion);
cbm_free_result(r);

/* Module-singleton delegation: _default.check() inside check. */
r = extract("class _Checker:\n"
" def check(self, app_id, tool_name):\n"
" return (True, \"ok\")\n"
"\n"
"_default = _Checker()\n"
"\n"
"def check(app_id, tool_name):\n"
" return _default.check(app_id, tool_name)\n",
CBM_LANG_PYTHON, "t", "gleipnir.py");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
/* The module-level wrapper (not the method) must stay clean. */
d = NULL;
for (int i = 0; i < r->defs.count; i++) {
if (r->defs.items[i].name && strcmp(r->defs.items[i].name, "check") == 0 &&
strcmp(r->defs.items[i].label, "Function") == 0) {
d = &r->defs.items[i];
}
}
ASSERT_NOT_NULL(d);
ASSERT_FALSE(d->is_recursive); /* _default is not this fn */
ASSERT_FALSE(d->unguarded_recursion);
cbm_free_result(r);

/* Same-named method on a local, called inside a loop (willow.nuke case):
* neither recursive nor recursion_in_loop. */
r = extract("def execute(conn, paths):\n"
" for p in paths:\n"
" cur = conn.cursor()\n"
" cur.execute(\"DELETE FROM t WHERE p = %s\", (p,))\n"
" return True\n",
CBM_LANG_PYTHON, "t", "nuke.py");
ASSERT_NOT_NULL(r);
ASSERT_FALSE(r->has_error);
d = find_def(r, "execute");
ASSERT_NOT_NULL(d);
ASSERT_FALSE(d->is_recursive); /* cur is not this fn */
ASSERT_FALSE(d->unguarded_recursion);
ASSERT_FALSE(d->recursion_in_loop);
cbm_free_result(r);
PASS();
}

/* Deep chained member access + parameter count structure smells. */
TEST(complexity_access_depth_and_params) {
CBMFileResult *r = extract("package p\n"
Expand Down Expand Up @@ -3736,6 +3804,7 @@ SUITE(extraction) {
RUN_TEST(complexity_self_receiver_still_recursive);
RUN_TEST(complexity_chained_receiver_not_self);
RUN_TEST(complexity_go_method_receiver_self_recursion);
RUN_TEST(complexity_delegation_receivers_not_recursive_issue876);
RUN_TEST(complexity_access_depth_and_params);
RUN_TEST(walk_defs_no_truncation_over_4096_issue668);
RUN_TEST(extract_rust_test_attr_marks_is_test_issue855);
Expand Down
Loading