Files
LEDMatrix/test/test_operation_queue_pending_and_trim.py
T
ChuckandClaude Opus 5.5 7e066174d9 chore: remove dead code, deprecate unused plugin APIs (over-engineering audit)
Whole-tree audit. Every symbol was checked against core, the plugin
monorepo and all eight third-party plugins in plugins.json first.

- Deprecate (removal 3.10.0) plugin-facing methods nothing calls:
  LogoDownloader bulk download, ConfigManager backup/secret wrappers,
  APIHelper extras, BackgroundDataService poll API, PluginManager /
  PluginStateManager info readers, and a few CacheManager, FontManager,
  BaseOddsManager, DynamicTeamResolver methods and PluginTestCase.
  plugin_api_usage.py learns their receiver names; DEPRECATIONS doc
  regenerated.
- Remove core-internal dead code: CacheMetrics, Vegas status/stats
  plumbing, sync "new cycle" message (followers ignore unknown types),
  unused operation types, test-only PluginCatalog readers, IPC to_dict
  and ping, _parse_form_value, CacheStrategyProtocol, ErrorAggregator
  callbacks, duplicate web response helpers.
- Web UI: drop never-mounted json-file-manager.js, the example widget,
  utils/error_handler.js, four uncalled PluginAPI methods, and 29
  escapeHtml shims (call window.LEDEscape directly). Public globals,
  BaseWidget and widget names unchanged.
- Remove six one-off scripts (owner decision) and the unused markupsafe
  and pytest-mock pins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:27:40 -04:00

72 lines
2.6 KiB
Python

"""PluginOperationQueue refuses a second queued op per plugin and stays bounded.
enqueue_operation only checked _active_operations, which holds the operation
that is *running*. While a plugin's first operation still waited in the queue
(the worker busy with another plugin), a second one for the same plugin was
accepted and both ran back to back. And _operations kept every operation ever
enqueued, although the history beside it was trimmed to max_history.
"""
import threading
import time
import pytest
from src.plugin_system.operation_queue import PluginOperationQueue
from src.plugin_system.operation_types import OperationStatus, OperationType
@pytest.fixture
def op_queue():
q = PluginOperationQueue(max_history=3)
yield q
q.shutdown()
def _wait_for(predicate, timeout=5.0):
deadline = time.monotonic() + timeout
while not predicate() and time.monotonic() < deadline:
time.sleep(0.01)
return predicate()
def test_second_pending_operation_for_a_plugin_is_refused(op_queue):
release = threading.Event()
started = threading.Event()
def blocker(op):
started.set()
release.wait(5)
return {"success": True}
op_queue.enqueue_operation(OperationType.INSTALL, "busy", operation_callback=blocker)
assert started.wait(5)
first = op_queue.enqueue_operation(
OperationType.INSTALL, "demo", operation_callback=lambda op: {"success": True})
assert op_queue.get_operation_status(first).status == OperationStatus.PENDING
with pytest.raises(ValueError, match="already has an active operation"):
op_queue.enqueue_operation(
OperationType.INSTALL, "demo", operation_callback=lambda op: {"success": True})
release.set()
assert _wait_for(lambda: op_queue.get_operation_status(first).status
== OperationStatus.COMPLETED)
# Once it has finished, the plugin accepts a new operation again.
op_queue.enqueue_operation(OperationType.UNINSTALL, "demo")
def test_operations_map_is_trimmed_with_history(op_queue):
ids = [op_queue.enqueue_operation(OperationType.INSTALL, f"p{i}",
operation_callback=lambda op: {"success": True})
for i in range(8)]
assert _wait_for(lambda: all(
(op_queue.get_operation_status(i) is None
or op_queue.get_operation_status(i).status == OperationStatus.COMPLETED)
for i in ids) and len(op_queue._operation_history) == 3)
assert len(op_queue._operations) == 3
kept = {op.operation_id for op in op_queue._operation_history}
assert set(op_queue._operations) == kept