Skip to content

chore: strip defensive getattrs from production, seed bypassed-init tests#81

Merged
AreteDriver merged 2 commits into
mainfrom
chore/strip-defensive-getattrs
May 4, 2026
Merged

chore: strip defensive getattrs from production, seed bypassed-init tests#81
AreteDriver merged 2 commits into
mainfrom
chore/strip-defensive-getattrs

Conversation

@AreteDriver
Copy link
Copy Markdown
Owner

Summary

Production code accumulated 8 sites of `getattr(self, '_x', default)` just to keep bypassed-`init` unit tests passing. This is debt item #2 from the post-v3.2.0 audit — the defensive code in production was the wrong place to handle the test-fixture gap.

Production changes

`src/argus_overview/ui/main_tab.py`:

  • Drop `getattr` around: `_replay_view_index`, `_replay_last_sample_ms`, `_replay_strip`, `_accent_color`, `_character_systems`, `_jump_calculator`, `_jump_max`, `_focus_window_id`, `status_dock`. Production `init` always sets these; tests own their own setup.
  • Also fixed a stale `calculator` local variable reference (pre-existing latent bug — the code path was only reachable when `alpha < 1.0` from a local that no longer existed).

Test infrastructure

New `tests/_test_helpers.py` exposes three seeders:

  • `seed_preview_widget(widget)` — `replay*` state
  • `seed_main_tab(tab)` — `_focus_window_id`, `status_dock` mock
  • `seed_window_manager(manager)` — `_character_systems`, `_jump_calculator`, `_jump_max`

9 bypassed-init test sites updated to call the relevant seeder.

Test plan

  • Full suite: 2398 passed, 5 skipped, 0 failures
  • `ruff check` + `ruff format --check` clean

Future-proofing

Anyone adding new state to `WindowPreviewWidget` / `MainTab` / `WindowManager` should add it to the appropriate seeder, not slap `getattr` on production reads.

🤖 Generated with Claude Code

…ests

Production code accumulated 8 sites of getattr(self, '_x', default) just
to keep bypassed-__init__ unit tests passing — patterns like
test_helpers that call WindowPreviewWidget.__new__(WindowPreviewWidget)
to test methods in isolation. The defensive code in production was the
wrong place to handle that gap.

This is debt item #2 from the post-v3.2.0 audit.

Production changes:
- main_tab.py: drop getattr around _replay_view_index,
  _replay_last_sample_ms, _replay_strip, _accent_color,
  _character_systems, _jump_calculator, _jump_max, _focus_window_id,
  status_dock. Production __init__ always sets these; tests are
  responsible for their own setup.
- main_tab.py: also fix a stale 'calculator' local variable reference
  that the strip exposed (pre-existing latent bug — code path was
  reachable only when alpha < 1.0 from a no-longer-defined local).

Test changes:
- New tests/_test_helpers.py exposes seed_preview_widget,
  seed_main_tab, seed_window_manager that initialize the v3.2.0
  attributes a bypassed-init instance needs.
- 9 bypassed-init test sites updated to call the relevant seeder.
- TestWindowManagerApplyThreatStateSmartFanout._make_manager already
  seeds char_systems; added _jump_calculator/_jump_max here too.

Suite: 2398 passed, 5 skipped, 0 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AreteDriver AreteDriver enabled auto-merge (squash) April 26, 2026 11:22
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the MainTab UI components to use direct attribute access, removing previous getattr and hasattr defensive guards. To support this change in tests where __init__ is bypassed, a new _test_helpers.py file was added to provide attribute seeding. Review feedback identifies that the seeding helpers are missing several required attributes, such as _accent_color and preview_frames, which will result in AttributeError during test execution.

Comment thread tests/_test_helpers.py
Comment on lines +21 to +27
def seed_preview_widget(widget) -> None:
"""Initialize the v3.2.0 attributes a bypassed-init WindowPreviewWidget
needs to survive update_frame, paintEvent, and threat-state methods."""
widget._replay_buffer = deque(maxlen=6)
widget._replay_last_sample_ms = 0
widget._replay_strip = None
widget._replay_view_index = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The seed_preview_widget helper is missing several attributes required by paintEvent and set_threat_state, which the docstring explicitly claims it supports. Since the production code now uses direct attribute access (removing getattr guards), calling these methods on a seeded widget will raise AttributeError.

Missing attributes include: _accent_color, _threat_level, _flash_color, _threat_alpha, _threat_distance, _pulse_phase, _positions_locked, _show_activity_indicator, and the various QTimer instances (_threat_decay_timer, _pulse_timer, _flash_timer). Also, it is better to use the REPLAY_BUFFER_SIZE constant instead of hardcoding 6.

def seed_preview_widget(widget) -> None:
    """Initialize the v3.2.0 attributes a bypassed-init WindowPreviewWidget
    needs to survive update_frame, paintEvent, and threat-state methods."""
    from PySide6.QtCore import QTimer
    from PySide6.QtGui import QColor
    from argus_overview.ui.main_tab import REPLAY_BUFFER_SIZE
    widget._replay_buffer = deque(maxlen=REPLAY_BUFFER_SIZE)
    widget._replay_last_sample_ms = 0
    widget._replay_strip = None
    widget._replay_view_index = None
    widget._accent_color = QColor(200, 200, 200)
    widget._threat_level = None
    widget._flash_color = None
    widget._threat_alpha = 0.0
    widget._threat_distance = None
    widget._pulse_phase = 0.0
    widget._positions_locked = False
    widget._show_activity_indicator = True
    widget._threat_decay_timer = QTimer()
    widget._pulse_timer = QTimer()
    widget._flash_timer = QTimer()

Comment thread tests/_test_helpers.py
Comment on lines +37 to +42
def seed_window_manager(manager) -> None:
"""Seed bypassed-init WindowManager attributes needed by
apply_threat_state and the smart fan-out / jumps-from filter."""
manager._character_systems = {}
manager._jump_calculator = None
manager._jump_max = 0
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The seed_window_manager helper should also initialize preview_frames to an empty dictionary. While it is a public attribute, it is initialized in __init__ and accessed directly in apply_threat_state. Bypassing __init__ without seeding it will cause an AttributeError when the loop at line 1524 in main_tab.py attempts to iterate over frames.

Suggested change
def seed_window_manager(manager) -> None:
"""Seed bypassed-init WindowManager attributes needed by
apply_threat_state and the smart fan-out / jumps-from filter."""
manager._character_systems = {}
manager._jump_calculator = None
manager._jump_max = 0
def seed_window_manager(manager) -> None:
"""Seed bypassed-init WindowManager attributes needed by
apply_threat_state and the smart fan-out / jumps-from filter."""
manager.preview_frames = {}
manager._character_systems = {}
manager._jump_calculator = None
manager._jump_max = 0

@AreteDriver AreteDriver merged commit 0bafc34 into main May 4, 2026
15 checks passed
@AreteDriver AreteDriver deleted the chore/strip-defensive-getattrs branch May 4, 2026 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant