Skip to content

test: make plugin test results mean something - #246

Merged
ChuckBuilds merged 3 commits into
mainfrom
test/honest-plugin-test-signal
Aug 5, 2026
Merged

test: make plugin test results mean something#246
ChuckBuilds merged 3 commits into
mainfrom
test/honest-plugin-test-signal

Conversation

@ChuckBuilds

@ChuckBuilds ChuckBuilds commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Test-only. No plugin code changes, no version bumps — so the safety-harness gate doesn't apply.

Why

These are standalone scripts that signal only through an exit code, so "not applicable here" was indistinguishable from "broken". That cost real time: the same seven baseball/football "failures" were re-baselined three separate times in one session just to prove a change hadn't caused them.

Categorised properly, the seven were:

count what they actually were
3 only a missing RGBMatrixEmulator in the runner's virtualenv — they pass in the core venv
1 a deliberate skip (test_score_antialiasing already exits 2)
2 interactive/hardware scripts — one prompts on stdin, one drives a matrix and loads the core config template
1 genuinely broken

The one that mattered

test_test_mode_live_games raised a _StopAfterTestUpdate sentinel from its fake, but suppressed only AttributeError. The sentinel escaped uncaught and the test died before reaching a single assertion — it had been silently verifying nothing for as long as it had been "failing", hidden in noise everyone had learned to ignore.

That is the actual cost of a noisy suite: not the wasted re-baselining, but that a real regression can hide in it indefinitely.

Fixed by suppressing both the sentinel and the buggy path's AttributeError. It now runs its invariants and passes.

The convention

0  pass
2  skip  — prerequisites absent (no tty, no matrix, no font, no optional dep)
1  fail  — a genuine problem

Scripts opt in by printing SKIP: <reason> and exiting 2. scripts/run_plugin_tests.py runs a plugin's scripts and reports pass/skip/fail, exiting non-zero only on real failures.

The two interactive scripts now pre-flight their prerequisites and skip rather than fail.

Result

baseball + football: 21 passed, 3 skipped, 0 failed — a clean signal for the first time.

Fleet-wide --all: 63 passed, 3 skipped, 7 failed. Those seven are left for a follow-up and are now legible rather than lumped together:

  • not applicable (2): a font that ships with the core; a missing optional astral dep
  • genuine staleness (5): a test importing BasketballPluginManager, which no longer exists; a MockLogger without setLevel; CacheManager called with a config_manager keyword the core no longer takes; a bare cache_manager import; two lacrosse assertions

The CacheManager one is worth a look on its own — a test calling a core API with a signature that has since changed is exactly the drift this repo's guarded-import discipline exists to catch.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5

Summary by CodeRabbit

  • New Features
    • Baseball Scoreboard scrolling now uses shared sports-display behavior while retaining compatibility with older core versions.
    • Added safer fallback handling for scrolling displays.
  • Bug Fixes
    • Reduced repetitive live-game logging for Baseball and Football Scoreboards through consolidated, throttled status updates.
  • Updates
    • Baseball Scoreboard updated to 1.22.0.
    • Football Scoreboard updated to 2.10.1.
    • Added release history and improved validation for plugin version information.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ChuckBuilds, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 080fb479-037b-430e-972c-15146d6a2d96

📥 Commits

Reviewing files that changed from the base of the PR and between e9b302b and d92078a.

📒 Files selected for processing (4)
  • plugins/baseball-scoreboard/test_baseball_plugin.py
  • plugins/baseball-scoreboard/test_test_mode_live_games.py
  • plugins/football-scoreboard/test_football_plugin.py
  • scripts/run_plugin_tests.py
📝 Walkthrough

Walkthrough

The PR migrates baseball scrolling to shared core classes with a legacy fallback, throttles live-content logging for both scoreboards, adds plugin test execution tooling, validates manifest fields in CI, and updates release metadata.

Changes

Scoreboard platform updates

Layer / File(s) Summary
Shared scroll orchestration
plugins/baseball-scoreboard/scroll_display.py, plugins/baseball-scoreboard/scroll_display_legacy.py
Baseball scrolling uses shared sports-scroll classes when available and a legacy implementation otherwise.
Live-content log throttling
plugins/*-scoreboard/manager.py, plugins/*-scoreboard/test_live_content_log_throttle.py, plugins/*-scoreboard/CHANGELOG.md
Both managers consolidate league counts and throttle unchanged log messages to one per minute. Regression tests cover state changes and repeated results.
Plugin test execution compatibility
plugins/*-scoreboard/test_*.py, scripts/run_plugin_tests.py
Plugin tests classify unavailable environments as skips. The runner standardizes execution, timeouts, output, and exit statuses.
Manifest version validation
scripts/check_manifest_version_fields.py, .github/workflows/test-plugins.yml
CI validates changed plugin manifests for deprecated fields and non-empty compatibility versions.
Plugin release metadata
plugins.json, plugins/*-scoreboard/manifest.json
Registry versions and plugin release records were updated for the Baseball and Football Scoreboards.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BaseballPlugin
  participant ScrollDisplayManager
  participant SportsScroll
  BaseballPlugin->>ScrollDisplayManager: prepare baseball game content
  ScrollDisplayManager->>SportsScroll: delegate shared display and frame management
  SportsScroll-->>ScrollDisplayManager: return scroll state and rendered frames
  ScrollDisplayManager-->>BaseballPlugin: provide display status and cached content
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: standardizing plugin test result signaling and making pass, skip, and failure outcomes meaningful.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch test/honest-plugin-test-signal
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/honest-plugin-test-signal

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codacy-production

codacy-production Bot commented Aug 4, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 critical · 1 medium · 1 minor

Alerts:
⚠ 4 issues (≤ 0 issues of at least minor severity)

Results:
4 new issues

Category Results
Security 1 minor
2 critical
1 medium

View in Codacy

🟢 Metrics 25 complexity

Metric Results
Complexity 25

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (2)
plugins/baseball-scoreboard/scroll_display.py (2)

209-215: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Return early when the renderer is missing.

_get_game_renderer() returns None when GameRenderer is unavailable. The loop then raises AttributeError on every game at Line 244 and logs a full traceback each time before the method returns False. One early return gives the same result with one log line.

♻️ Proposed early return
             renderer = self._get_game_renderer(game_card_width)
+            if renderer is None:
+                self.logger.error("GameRenderer not available; cannot prepare scroll content")
+                return False
 
             # Pass rankings cache to renderer if available
-            if renderer and rankings_cache:
+            if rankings_cache:
                 renderer.set_rankings_cache(rankings_cache)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/baseball-scoreboard/scroll_display.py` around lines 209 - 215, In the
game-rendering loop, update the flow immediately after
_get_game_renderer(game_card_width) to return False when the renderer is None,
logging the unavailability once before returning. Keep the rankings_cache
assignment and subsequent rendering logic unchanged for valid renderers,
preventing later renderer calls from raising AttributeError for each game.

298-304: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use explicit Optional for rankings_cache. All three signatures declare rankings_cache: Dict[str, int] = None. PEP 484 prohibits implicit Optional, and Ruff reports RUF013 at each site. The prepare_scroll_content signatures already use Optional[Dict[str, int]], so this is also an internal inconsistency.

  • plugins/baseball-scoreboard/scroll_display.py#L298-L304: change the prepare_content parameter to rankings_cache: Optional[Dict[str, int]] = None.
  • plugins/baseball-scoreboard/scroll_display_legacy.py#L604-L610: change the prepare_and_display parameter to rankings_cache: Optional[Dict[str, int]] = None.
  • plugins/baseball-scoreboard/scroll_display_legacy.py#L630-L636: change the prepare_content parameter to rankings_cache: Optional[Dict[str, int]] = None.

Optional is already imported in both files.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/baseball-scoreboard/scroll_display.py` around lines 298 - 304,
Replace the implicit nullable rankings_cache annotations with Optional[Dict[str,
int]] = None in prepare_content in plugins/baseball-scoreboard/scroll_display.py
at lines 298-304, prepare_and_display in
plugins/baseball-scoreboard/scroll_display_legacy.py at lines 604-610, and
prepare_content in plugins/baseball-scoreboard/scroll_display_legacy.py at lines
630-636. Optional is already imported; no other changes are needed.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@plugins/baseball-scoreboard/scroll_display_legacy.py`:
- Around line 581-583: Replace the undefined ScrollDisplay annotation in
LegacyScrollDisplayManager.__init__ with the locally defined LegacyScrollDisplay
type (or a safely deferred annotation), ensuring construction works on legacy
cores. Add a construction test for LegacyScrollDisplayManager to exercise the
fallback path.

In `@plugins/football-scoreboard/manager.py`:
- Around line 1978-1996: Update the live-content flow around has_live_content()
in plugins/football-scoreboard/manager.py:1978-1996 to include active
celebration status in the throttled state and INFO log path rather than
returning before it, preserving correct transition logging into and out of
celebration. Add a regression test in
plugins/football-scoreboard/test_live_content_log_throttle.py:86-143 covering an
active celebration and its transition back to normal live-content state.

In `@plugins/football-scoreboard/manifest.json`:
- Around line 27-31: Rename the minimum-version key in the Football manifest’s
lowest-minimum versions entry from ledmatrix_min to ledmatrix_min_version,
leaving any other legacy ledmatrix_min entries unchanged.

In `@scripts/check_manifest_version_fields.py`:
- Around line 61-66: Update the validation condition in the manifest-checking
logic around OLD and NEW so versions[0] must contain NEW and must not contain
OLD. Report a problem when NEW is missing or OLD is present, including the
duplicate-field case where both exist, while preserving the existing message
context and allowance for older entries.

In `@scripts/run_plugin_tests.py`:
- Around line 92-95: Update the plugin ID handling in main() before aggregating
test scripts: validate each explicitly requested ID against the available
plugins, and return FAIL when any ID is unknown or has no corresponding plugin
tests. Preserve the existing aggregation and execution behavior for valid plugin
IDs.
- Around line 52-59: Establish one explicit core-checkout contract across both
sites: in scripts/run_plugin_tests.py lines 52-59, resolve the supplied core
path relative to the runner’s original invocation context and pass it to the
child through a dedicated environment variable or argument; in
plugins/baseball-scoreboard/test_baseball_plugin.py lines 393-396, update the
pre-flight configuration-template check to read and use that explicit core path
instead of resolving config/config.template.json from the child working
directory.

---

Nitpick comments:
In `@plugins/baseball-scoreboard/scroll_display.py`:
- Around line 209-215: In the game-rendering loop, update the flow immediately
after _get_game_renderer(game_card_width) to return False when the renderer is
None, logging the unavailability once before returning. Keep the rankings_cache
assignment and subsequent rendering logic unchanged for valid renderers,
preventing later renderer calls from raising AttributeError for each game.
- Around line 298-304: Replace the implicit nullable rankings_cache annotations
with Optional[Dict[str, int]] = None in prepare_content in
plugins/baseball-scoreboard/scroll_display.py at lines 298-304,
prepare_and_display in plugins/baseball-scoreboard/scroll_display_legacy.py at
lines 604-610, and prepare_content in
plugins/baseball-scoreboard/scroll_display_legacy.py at lines 630-636. Optional
is already imported; no other changes are needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ed69cc84-ec0d-47cf-9f48-102a2e377b7b

📥 Commits

Reviewing files that changed from the base of the PR and between 5ebd488 and e9b302b.

📒 Files selected for processing (17)
  • .github/workflows/test-plugins.yml
  • plugins.json
  • plugins/baseball-scoreboard/CHANGELOG.md
  • plugins/baseball-scoreboard/manager.py
  • plugins/baseball-scoreboard/manifest.json
  • plugins/baseball-scoreboard/scroll_display.py
  • plugins/baseball-scoreboard/scroll_display_legacy.py
  • plugins/baseball-scoreboard/test_baseball_plugin.py
  • plugins/baseball-scoreboard/test_live_content_log_throttle.py
  • plugins/baseball-scoreboard/test_test_mode_live_games.py
  • plugins/football-scoreboard/CHANGELOG.md
  • plugins/football-scoreboard/manager.py
  • plugins/football-scoreboard/manifest.json
  • plugins/football-scoreboard/test_football_plugin.py
  • plugins/football-scoreboard/test_live_content_log_throttle.py
  • scripts/check_manifest_version_fields.py
  • scripts/run_plugin_tests.py

Comment on lines +581 to +583
# Create scroll displays for each game type
self._scroll_displays: Dict[str, ScrollDisplay] = {}
self._current_game_type: Optional[str] = None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

ScrollDisplay is undefined; the legacy fallback raises NameError on construction.

This module defines LegacyScrollDisplay, not ScrollDisplay. Python evaluates the annotation of an attribute target at runtime, so Dict[str, ScrollDisplay] is evaluated when LegacyScrollDisplayManager.__init__ runs, and it raises NameError: name 'ScrollDisplay' is not defined.

The alias in scroll_display.py Line 61 binds ScrollDisplay in that module's namespace only. It does not resolve this reference.

Effect: on any core that predates src.common.sports_scroll, every LegacyScrollDisplayManager construction fails. plugins/baseball-scoreboard/manager.py catches the exception and sets self._scroll_manager = None, so scroll mode is silently disabled. This defeats the fallback that this change adds.

🐛 Proposed fix
         # Create scroll displays for each game type
-        self._scroll_displays: Dict[str, ScrollDisplay] = {}
+        self._scroll_displays: Dict[str, LegacyScrollDisplay] = {}
         self._current_game_type: Optional[str] = None

Add a construction test for LegacyScrollDisplayManager so the fallback path is exercised in CI. Do you want me to generate that test?

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Create scroll displays for each game type
self._scroll_displays: Dict[str, ScrollDisplay] = {}
self._current_game_type: Optional[str] = None
# Create scroll displays for each game type
self._scroll_displays: Dict[str, LegacyScrollDisplay] = {}
self._current_game_type: Optional[str] = None
🧰 Tools
🪛 Ruff (0.16.0)

[error] 582-582: Undefined name ScrollDisplay

(F821)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/baseball-scoreboard/scroll_display_legacy.py` around lines 581 - 583,
Replace the undefined ScrollDisplay annotation in
LegacyScrollDisplayManager.__init__ with the locally defined LegacyScrollDisplay
type (or a safely deferred annotation), ensuring construction works on legacy
cores. Add a construction test for LegacyScrollDisplayManager to exercise the
fallback path.

Source: Linters/SAST tools

Comment thread plugins/football-scoreboard/manager.py
Comment on lines +27 to +31
{
"released": "2026-08-03",
"version": "2.10.1",
"ledmatrix_min": "2.0.0"
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 'ledmatrix_min(_version)?|deprecated|newest|latest' \
  scripts/check_manifest_version_fields.py \
  plugins/baseball-scoreboard/manifest.json \
  plugins/football-scoreboard/manifest.json

Repository: ChuckBuilds/ledmatrix-plugins

Length of output: 50385


Rename the lowest-minimum legacy minimum-version key to ledmatrix_min_version.

ledmatrix_min is the deprecated spelling, but the Football manifest still uses it as the minimum versions[] entry. Update Line 30 to ledmatrix_min_version; older ledmatrix_min entries may remain as-is.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugins/football-scoreboard/manifest.json` around lines 27 - 31, Rename the
minimum-version key in the Football manifest’s lowest-minimum versions entry
from ledmatrix_min to ledmatrix_min_version, leaving any other legacy
ledmatrix_min entries unchanged.

Comment on lines +61 to +66
if OLD in head and NEW not in head:
problems.append(
f"{plugin_id}: versions[0] ({version_label}) uses the deprecated "
f"'{OLD}'. Rename it to '{NEW}' — this is the entry the store and "
f"loader actually read. Older entries can stay as they are."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject missing and duplicate minimum-version fields.

Line 61 reports ledmatrix_min only when ledmatrix_min_version is absent. A newest entry with both fields passes. A newest entry with neither field also passes. Both cases bypass the stated requirement for versions[0] to use ledmatrix_min_version.

Require ledmatrix_min_version and reject ledmatrix_min whenever it is present.

Proposed fix
-    if OLD in head and NEW not in head:
+    if NEW not in head:
         problems.append(
-            f"{plugin_id}: versions[0] ({version_label}) uses the deprecated "
-            f"'{OLD}'. Rename it to '{NEW}' — this is the entry the store and "
-            f"loader actually read. Older entries can stay as they are."
+            f"{plugin_id}: versions[0] ({version_label}) is missing '{NEW}'."
         )
+    if OLD in head:
+        problems.append(
+            f"{plugin_id}: versions[0] ({version_label}) contains deprecated "
+            f"'{OLD}'. Remove it and use only '{NEW}'."
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if OLD in head and NEW not in head:
problems.append(
f"{plugin_id}: versions[0] ({version_label}) uses the deprecated "
f"'{OLD}'. Rename it to '{NEW}' — this is the entry the store and "
f"loader actually read. Older entries can stay as they are."
)
if NEW not in head:
problems.append(
f"{plugin_id}: versions[0] ({version_label}) is missing '{NEW}'."
)
if OLD in head:
problems.append(
f"{plugin_id}: versions[0] ({version_label}) contains deprecated "
f"'{OLD}'. Remove it and use only '{NEW}'."
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/check_manifest_version_fields.py` around lines 61 - 66, Update the
validation condition in the manifest-checking logic around OLD and NEW so
versions[0] must contain NEW and must not contain OLD. Report a problem when NEW
is missing or OLD is present, including the duplicate-field case where both
exist, while preserving the existing message context and allowance for older
entries.

Comment thread scripts/run_plugin_tests.py
Comment thread scripts/run_plugin_tests.py
claude added 2 commits August 4, 2026 14:29
Every one of these standalone scripts signals only through an exit code, and
without a shared convention "not applicable here" was indistinguishable from
"broken". That cost real time: the same seven baseball/football "failures"
were re-baselined three separate times in one session to prove a change had
not caused them.

Categorising them properly, the seven were:

  3  only a missing RGBMatrixEmulator in the runner's virtualenv -- they pass
     in the core venv
  1  a deliberate skip (test_score_antialiasing already exits 2)
  2  interactive/hardware scripts: one prompts on stdin, one drives a real or
     emulated matrix and loads the core config template
  1  genuinely broken

That last one is the reason this matters. test_test_mode_live_games raised a
_StopAfterTestUpdate sentinel from its fake, but suppressed only
AttributeError -- so the sentinel escaped and the test died before reaching a
single assertion. It had been silently verifying nothing for as long as it had
been "failing", hidden in noise everyone had learned to ignore. Fixed by
suppressing both the sentinel and the buggy path's AttributeError; it now runs
its invariants and passes.

The convention: 0 pass, 2 skip (prerequisites absent), 1 fail. Scripts opt in
by printing "SKIP: <reason>" and exiting 2. scripts/run_plugin_tests.py runs a
plugin's scripts and reports pass/skip/fail, exiting non-zero only on real
failures.

The two interactive scripts now pre-flight their prerequisites and skip
instead of failing.

baseball + football: 21 passed, 3 skipped, 0 failed -- a clean signal for the
first time.

Fleet-wide `--all` reports 63 passed, 3 skipped, 7 failed. Those seven are
left for a follow-up and are now legible rather than lumped together: two are
"not applicable" (a font that ships with the core, a missing optional astral
dep) and five look like genuine staleness -- a test importing a class name
that no longer exists, a MockLogger without setLevel, a CacheManager called
with a keyword the core no longer takes, a bare cache_manager import, and two
lacrosse assertions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
Running it under a bare `python3` that lacks the plugins' dependencies reports
every suite as failed -- 22 failures where the only thing wrong was the
interpreter. Exactly the misclassification this script was written to end, so
it should not be the thing that causes it.

Scripts are spawned with sys.executable, and --core only sets PYTHONPATH.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
Both findings on #246 reproduce.

Unknown plugin ids looked like success. `run_plugin_tests.py a-typo` found no
scripts, ran nothing, and returned 0. A typo or a removed plugin therefore
reported clean. Unknown ids now fail.

--core did not reach the scripts. It only prepended to PYTHONPATH, while
children run with cwd set to the plugin directory -- so a script resolving a
core asset relatively looked in the wrong place and skipped even though a core
had been supplied. LEDMATRIX_CORE now carries the resolved absolute path, and
baseball's pre-flight uses it, chdir'ing to the core because the core's own
ConfigManager and DisplayManager resolve config/ and assets/ relatively too.

That fix turns a SKIP into a FAIL, and the FAIL is correct: with the core
finally reachable, test_baseball_plugin gets far enough to show it has drifted
from the plugin API. It checks `plugin.initialized`, which no longer exists,
and then `plugin.leagues`, which does not either. The script has been skipping
for long enough that nobody noticed it had gone stale.

I briefly patched around `initialized` and reverted it. Chasing the drift is a
different job, and papering over it would be the same misclassification this
whole change set exists to end -- a script that cannot run reported as fine.
It belongs in the stale-test tranche this PR already catalogues, now with a
concrete cause rather than "needs a LEDMatrix tree".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5
@ChuckBuilds
ChuckBuilds merged commit db5e58a into main Aug 5, 2026
2 of 4 checks passed
@ChuckBuilds
ChuckBuilds deleted the test/honest-plugin-test-signal branch August 5, 2026 14:53
ChuckBuilds added a commit that referenced this pull request Aug 5, 2026
…0) (#247)

* feat(football-scoreboard): adopt the core scroll orchestration (2.11.0)

Second B5 adoption, and the second of the three lineages (baseball is the
hockey/lacrosse/baseball/basketball/ufc family; football is its own).

Orchestration -- scroll-helper configuration, frame pumping, completion,
settings resolution, native global_config['target_fps'] -- now comes from the
core's src.common.sports_scroll. Only the football-specific content half stays
here: game cards and league separator icons. scroll_display.py goes 679 -> 310
lines.

Guarded, exactly as baseball: scroll_display.py selects between the
core-backed subclass and scroll_display_legacy (the previous implementation,
frozen) at import. The except clause matches only the core module's own
absence, so a failure raised inside a present-but-broken core surfaces rather
than silently downgrading.

The floor stays at 2.0.0. With a working fallback the plugin does not require
3.2.0, it prefers it, so nobody is cut off from updates; the floor rises at B6
when the fallback is deleted. versions[0] uses ledmatrix_min_version.

Football needed less than baseball did: its __init__ sets only attributes the
core base already provides, so no custom __init__. It does pin game_card_width
at 128 where core sizes cards to the panel, so scroll_settings_defaults()
overrides that -- the byte-identical gate depends on it.

Content methods were lifted verbatim from the legacy source rather than
retyped.

Verified:
- all 16 harness renders (8 sizes x 2 screens) byte-for-byte identical to
  2.10.1: `diff -r` of the before/after output directories is empty
- test_core_fallback.py added, asserting all three configurations this plugin
  can meet, and checked that it bites: sabotaging the guard to match the
  unguarded recipe makes it exit 1, the healthy tree exits 0
- 10 of 11 suites pass; the one failure is test_football_plugin.py, an
  interactive script whose skip guard is in the not-yet-merged #246
- module-collision check clean across 42 plugins

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5

* test(football-scoreboard): resolvability check, and fix the stale annotation

Brings this plugin's fallback test up to the version that found the missing
imports in the other six -- it resolves the globals each method reads, across
both the display and manager classes and on both import paths, rather than
checking that method names exist. This plugin's header was hand-written and is
already clean; the point is that the old check could not have told us that.

Also corrects the legacy manager's `Dict[str, ScrollDisplay]` annotation to
the post-rename name. On Python 3.13 that annotation is not evaluated, so the
reported NameError does not reproduce there, but it is wrong regardless and
this repo supports 3.10 through 3.13.

Not changed: get_all_vegas_content_items. It is not lost -- the core-backed
manager inherits it from SportsScrollDisplayManager, and diffing the two shows
the only differences are the type annotation, docstring, loop variable name
and quote style. The logic is identical.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Udr6MfaFLUPhX5Fgo67Jf5

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

2 participants