Files
cleveragents-core/tests/auto_agents/test_implementer_prefetch.py
T
drew 0bc734c020 style: ruff format the controller-state-machine branch (288 files)
Applies `ruff format` to the accumulated formatting debt on this branch.
Formatting-only — no behavioral changes. Required for CI/lint's format
gate (`nox -s format -- --check`), which the branch was failing on 288
tracked files that drifted from ruff's canonical style.

In-progress WIP files are intentionally excluded so this commit stays a
clean formatting-only diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:09:17 -04:00

328 lines
14 KiB
Python

"""Unit tests for ``tools/_implementer_prefetch.py`` helpers.
Focused on the bounded comment-carry logic added for run-8 findings
R8-2 / R8-3: ``_bounded_comment_view`` decides which comments reach
the worker prompt + sentinel verbatim — the most-recent N plus every
non-bot comment — while the bot's older attempt comments are left to
the ``_attempt_history`` digest.
"""
from __future__ import annotations
import pytest
from .conftest import load_tool_module
@pytest.fixture
def prefetch_mod():
return load_tool_module("_implementer_prefetch")
def _attempt_comment(i: int) -> dict:
"""A bot ``**Implementation Attempt**`` comment (digested, not
carried verbatim once it ages past the recent window)."""
return {
"id": i,
"created_at": "2026-01-01T00:00:00Z",
"body": f"**Implementation Attempt** — Tier 0: qwen — Failed\n\nattempt {i}",
}
def _human_comment(i: int) -> dict:
"""A human / reviewer comment — always carried verbatim."""
return {
"id": i,
"created_at": "2026-01-01T00:00:00Z",
"body": f"human comment {i}",
"user": {"login": "drew"},
}
def _bot_status_comment(i: int, login: str = "HAL9000") -> dict:
"""A bot status / claim / sentinel comment — bot-authored but does
NOT carry the structured ``**Implementation Attempt**`` marker
(so a content-only classifier misses it). On a bot-heavy PR like
#30 these dominate the comment list and would balloon a naive
'keep all non-attempt' view (run-10 inspection)."""
return {
"id": i,
"created_at": "2026-01-01T00:00:00Z",
"body": f"<!-- merge_drive.py: claim --> status update {i}",
"user": {"login": login},
}
class TestBoundedCommentView:
def test_under_cap_returns_all(self, prefetch_mod):
comments = [_attempt_comment(i) for i in range(10)]
assert prefetch_mod._bounded_comment_view(comments, 50) == comments
def test_over_cap_all_bot_returns_last_n(self, prefetch_mod):
comments = [_attempt_comment(i) for i in range(120)]
view = prefetch_mod._bounded_comment_view(comments, 50)
assert [c["id"] for c in view] == list(range(70, 120))
def test_human_comments_always_kept(self, prefetch_mod):
# 120 comments, all bot attempts except ids 5 and 10 (human).
# cap=50 → view = the two early human comments + the last 50,
# order preserved.
comments = [
_human_comment(i) if i in (5, 10) else _attempt_comment(i)
for i in range(120)
]
view = prefetch_mod._bounded_comment_view(comments, 50)
assert [c["id"] for c in view] == [5, 10] + list(range(70, 120))
def test_human_in_recent_window_not_duplicated(self, prefetch_mod):
comments = [_attempt_comment(i) for i in range(120)]
comments[100] = _human_comment(100) # human AND inside the last 50
view = prefetch_mod._bounded_comment_view(comments, 50)
ids = [c["id"] for c in view]
assert ids == list(range(70, 120))
assert ids.count(100) == 1
def test_empty(self, prefetch_mod):
assert prefetch_mod._bounded_comment_view([], 50) == []
def test_bot_status_comments_dropped_by_author(self, prefetch_mod):
"""Run-10 bug: an older comment authored by a known bot login
but lacking the structured attempt marker (claim / status /
sentinel posts, etc.) must be DROPPED from the view, not kept
as "human." On PR #30 those non-attempt bot comments were
1240 of 1480 and ballooned the bounded view to 1252 items.
Author-based classification catches them; content-only does not.
"""
# 200 bot status comments (claim/sentinel/etc., no attempt marker)
# — these are exactly the comments the old content-only rule
# mis-classified as "human" and kept.
comments = [_bot_status_comment(i) for i in range(200)]
view = prefetch_mod._bounded_comment_view(
comments,
50,
bot_logins=("HAL9000", "HAL9001"),
)
# Only the most-recent 50 survive — everything else was bot-
# authored noise.
assert [c["id"] for c in view] == list(range(150, 200))
def test_bot_and_human_mixed_collapses_to_cap_plus_humans(
self,
prefetch_mod,
):
"""End-to-end: a realistic bot-heavy PR shape — 200 bot status
comments, 200 bot attempt comments, 3 real human comments
scattered through. The view should collapse to (real humans) +
(the recent N), regardless of how many bot comments exist.
"""
comments: list[dict] = []
# Interleave 200 status + 200 attempt comments, with humans
# at positions 50, 150, 250 (well outside the recent-N window
# so we verify they survive on author/content grounds, not on
# recency).
human_positions = {50, 150, 250}
next_human = 0
for i in range(403):
if i in human_positions:
comments.append(_human_comment(next_human))
next_human += 1
elif i % 2 == 0:
comments.append(_bot_status_comment(i))
else:
comments.append(_attempt_comment(i))
view = prefetch_mod._bounded_comment_view(
comments,
50,
bot_logins=("HAL9000", "HAL9001"),
)
# Real humans before the recent window + the last 50 comments.
# Recent window starts at index 403 - 50 = 353, so all 3 humans
# (at 50, 150, 250) are pre-window and survive on author check.
human_ids_in_view = [
c["id"]
for c in view
if isinstance(c.get("user"), dict) and c["user"].get("login") == "drew"
]
assert human_ids_in_view == [0, 1, 2]
# Total view size: 3 humans + 50 recent. The recent 50 may
# contain a mix of bots and (no) humans (no human positions
# in [353, 402]); the size is bounded.
assert len(view) == 3 + 50
def test_explicit_empty_bot_logins_keeps_unidentified_bot_status(
self,
prefetch_mod,
):
"""When the caller passes ``bot_logins=()`` explicitly (env-less
environments without the safety-net fallback in play), the
author check is a no-op — the belt-and-braces ``is_attempt_comment``
still drops structured attempts, but bot status comments that
lack the marker survive. Documents the failure mode if the env
is misconfigured AND no fallback fires; the production default
path always has at least the HAL9000/HAL9001 fallback so this
scenario does not occur outside tests."""
comments = [_bot_status_comment(i) for i in range(120)]
view = prefetch_mod._bounded_comment_view(
comments,
50,
bot_logins=(),
)
# Without author detection, status comments masquerade as
# humans and all 120 survive.
assert len(view) == 120
class TestPrefetchResultFields:
def test_new_comment_fields_present_with_defaults(self, prefetch_mod):
r = prefetch_mod.ImplementerPrefetchResult()
assert r.pr_comments_view == []
assert r.pr_comments_digest == {}
class TestFetchPrContextWiring:
"""``_fetch_pr_context`` must derive BOTH ``pr_comments_view`` and
``pr_comments_digest`` from the full comment list it gets back from
the cache — the full list stays on ``pr_comments`` (the dedup path
in dispatch_implementer needs it), the bounded view + digest are
what the prompt / sentinel consume. The section-ordering tests
drive ``build_*_prompt`` with a hand-built result, so this is the
only place the actual fetcher wiring is asserted (run-8 R8-2/R8-3).
"""
def test_view_and_digest_derived_from_full_comment_list(
self, prefetch_mod, monkeypatch, tmp_path
):
from .conftest import make_dispatch_config
# 122 comments: a human at the very start, 120 bot attempt
# comments, a human at the very end. cap=50 → the bounded view
# is the early human (kept because non-bot) + the last 50.
comments = (
[_human_comment(0)]
+ [_attempt_comment(i) for i in range(1, 121)]
+ [_human_comment(121)]
)
cap = prefetch_mod.DEFAULT_MAX_PROMPT_COMMENTS
# Stub every Forgejo round-trip _fetch_pr_context makes before
# the comments fetch so the test is hermetic and fast. Only
# pr_details must be non-None (a None there early-returns).
monkeypatch.setattr(
prefetch_mod._review_fetch,
"fetch_pr_details",
lambda cfg, n: {
"number": n,
"title": "t",
"head": {"sha": "abc", "ref": "feature/x"},
},
)
monkeypatch.setattr(
prefetch_mod._pr_diff,
"fetch_pr_diff_detailed",
lambda cfg, n: ("diff --git a/x b/x\n", False, "", {}),
)
monkeypatch.setattr(
prefetch_mod._review_fetch,
"fetch_ci_status",
lambda cfg, sha: None,
)
# The cache hands back the FULL list; the wiring under test is
# what _fetch_pr_context does with it.
monkeypatch.setattr(
prefetch_mod._pr_comments_cache,
"get_pr_comments",
lambda cfg, n: (comments, True),
)
cfg = make_dispatch_config(tmp_path)
result = prefetch_mod.fetch_pr_fix_context(cfg, {"number": 30, "title": "t"})
# Full list preserved verbatim on pr_comments.
assert result.pr_comments == comments
# Bounded view = exactly _bounded_comment_view's output:
# early human + the last cap comments, order preserved.
assert result.pr_comments_view == prefetch_mod._bounded_comment_view(
comments, cap
)
assert len(result.pr_comments_view) == cap + 1
assert result.pr_comments_view[0] == _human_comment(0)
assert _human_comment(121) in result.pr_comments_view
# An old bot attempt is dropped from the view (it survives only
# via the digest rollup); a recent one is kept. ``_attempt_comment``
# bodies end with ``attempt {i}``, so match on the suffix.
view_bodies = [c["body"] for c in result.pr_comments_view]
assert not any(b.endswith("attempt 1") for b in view_bodies)
assert any(b.endswith("attempt 120") for b in view_bodies)
# Digest is computed from the FULL list, not the bounded view.
digest = result.pr_comments_digest
assert digest["total_comments"] == 122
assert digest["attempt_count"] == 120
assert digest["non_attempt_count"] == 2
assert digest["by_tier"] == {0: 120}
assert "rendered" in digest
class TestAssessPromptCompleteness:
"""W8 harvest (2026-05-15) — translate the prefetch carrier's
per-section completion flags into a single loud-signal dict."""
def _mod(self):
return load_tool_module("_implementer_prefetch", fresh=True)
def test_fully_complete_result_is_not_degraded(self):
"""Happy path: a fresh ImplementerPrefetchResult (all defaults
True) is not degraded."""
mod = self._mod()
result = mod.ImplementerPrefetchResult()
out = mod.assess_prompt_completeness(result)
assert out["degraded"] is False
assert out["missing_sections"] == []
assert out["error_kinds"] == []
def test_single_failed_section_marks_degraded(self):
"""Failure case: one section's completed-flag is False —
degraded=True and that section is listed."""
mod = self._mod()
result = mod.ImplementerPrefetchResult()
result.ci_status_completed = False
result.error_kinds.append("ci_status:transient")
out = mod.assess_prompt_completeness(result)
assert out["degraded"] is True
assert "ci_status" in out["missing_sections"]
assert "ci_status:transient" in out["error_kinds"]
def test_truncated_diff_is_called_out_separately(self):
"""Failure case: a truncated diff (bytes present but
bounded) shows up as ``diff_truncated`` in missing_sections
— operators need to see this even though
diff_completed remains True for the same row."""
mod = self._mod()
result = mod.ImplementerPrefetchResult()
result.diff_truncated = True
out = mod.assess_prompt_completeness(result)
assert out["degraded"] is True
assert "diff_truncated" in out["missing_sections"]
def test_multiple_failures_aggregate(self):
"""Failure case: when multiple sections fail, all are listed
— operators see the full picture, not just the first
failure."""
mod = self._mod()
result = mod.ImplementerPrefetchResult()
result.pr_details_completed = False
result.linked_issues_completed = False
out = mod.assess_prompt_completeness(result)
assert out["degraded"] is True
assert set(out["missing_sections"]) == {"pr_details", "linked_issues"}
def test_data_complete_false_with_no_section_flags_still_degraded(self):
"""Failure case: defensive — if a caller flipped
``data_complete=False`` directly without touching the
per-section flags (legacy code path), the aggregate still
surfaces as degraded so the loud-signal does not miss it."""
mod = self._mod()
result = mod.ImplementerPrefetchResult()
result.data_complete = False
out = mod.assess_prompt_completeness(result)
assert out["degraded"] is True