mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-20 18:51:26 -03:00
8d46d26abe
The four tests that wait on the background debounced write race against SAVE_DELAY (1.0s): _wait_for_save polls 100 x 0.01s = 1.0s, exactly equal to the debounce, leaving zero slack. On a loaded CI runner the write lands after the poll gives up, failing intermittently with 'Recipe open stats file was never written' (5 of 62 backend runs since the tests landed). Shrink SAVE_DELAY to 0.05s in _prepare so the write lands ~20x inside the poll window. The debounce duration is not what these tests verify; production default stays 1.0s.
165 lines
5.4 KiB
Python
165 lines
5.4 KiB
Python
import asyncio
|
|
import contextlib
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from py.utils import recipe_open_stats as stats_module
|
|
from py.utils.recipe_open_stats import RecipeOpenStats
|
|
|
|
|
|
async def _finalize(tasks) -> None:
|
|
for task in tasks:
|
|
task.cancel()
|
|
with contextlib.suppress(asyncio.CancelledError):
|
|
await task
|
|
RecipeOpenStats._instance = None
|
|
|
|
|
|
def _prepare(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
|
RecipeOpenStats._instance = None
|
|
settings_dir = tmp_path / "settings"
|
|
settings_dir.mkdir(parents=True, exist_ok=True)
|
|
monkeypatch.setattr(
|
|
stats_module, "get_settings_dir", lambda create=True: str(settings_dir)
|
|
)
|
|
# Shrink the debounce well below the 100 x 0.01s poll window in
|
|
# _wait_for_save so the background write always lands with a wide margin.
|
|
# The debounce duration is not what these tests verify; SAVE_DELAY stays
|
|
# at its production default (1.0s) outside this test module.
|
|
monkeypatch.setattr(RecipeOpenStats, "SAVE_DELAY", 0.05)
|
|
created_tasks = []
|
|
real_create_task = stats_module.asyncio.create_task
|
|
|
|
def _track_task(coro):
|
|
task = real_create_task(coro)
|
|
created_tasks.append(task)
|
|
return task
|
|
|
|
monkeypatch.setattr(stats_module.asyncio, "create_task", _track_task)
|
|
return RecipeOpenStats(), created_tasks, settings_dir
|
|
|
|
|
|
async def _wait_for_save(stats_file: Path) -> None:
|
|
for _ in range(100):
|
|
if stats_file.exists():
|
|
return
|
|
await asyncio.sleep(0.01)
|
|
raise AssertionError("Recipe open stats file was never written")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_record_open_persists_timestamp(tmp_path, monkeypatch):
|
|
stats, tasks, settings_dir = _prepare(tmp_path, monkeypatch)
|
|
stats_file = settings_dir / "stats" / RecipeOpenStats.STATS_FILENAME
|
|
|
|
stats.record_open("abc-123")
|
|
await _wait_for_save(stats_file)
|
|
|
|
data = json.loads(stats_file.read_text(encoding="utf-8"))
|
|
assert isinstance(data["abc-123"], float)
|
|
await _finalize(tasks)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_record_open_updates_existing_entry(tmp_path, monkeypatch):
|
|
stats, tasks, settings_dir = _prepare(tmp_path, monkeypatch)
|
|
stats_file = settings_dir / "stats" / RecipeOpenStats.STATS_FILENAME
|
|
|
|
stats.record_open("r1")
|
|
await _wait_for_save(stats_file)
|
|
first = json.loads(stats_file.read_text(encoding="utf-8"))["r1"]
|
|
|
|
await asyncio.sleep(0.01)
|
|
stats.record_open("r1")
|
|
await stats.save_stats(force=True)
|
|
|
|
second = json.loads(stats_file.read_text(encoding="utf-8"))["r1"]
|
|
assert second > first
|
|
await _finalize(tasks)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_opened_map_reloads_on_file_change(tmp_path, monkeypatch):
|
|
stats, tasks, settings_dir = _prepare(tmp_path, monkeypatch)
|
|
stats_file = settings_dir / "stats" / RecipeOpenStats.STATS_FILENAME
|
|
|
|
stats.record_open("r1")
|
|
await _wait_for_save(stats_file)
|
|
|
|
stats_file.write_text(json.dumps({"r2": 500.0}), encoding="utf-8")
|
|
opened_map = stats.get_opened_map()
|
|
assert opened_map == {"r2": 500.0}
|
|
await _finalize(tasks)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_merges_entries_written_by_another_process(tmp_path, monkeypatch):
|
|
stats, tasks, settings_dir = _prepare(tmp_path, monkeypatch)
|
|
stats_file = settings_dir / "stats" / RecipeOpenStats.STATS_FILENAME
|
|
|
|
stats.record_open("r1")
|
|
await _wait_for_save(stats_file)
|
|
first_ts = json.loads(stats_file.read_text(encoding="utf-8"))["r1"]
|
|
|
|
# Another process writes its own entry plus a newer timestamp for r1
|
|
stats_file.write_text(
|
|
json.dumps({"r1": first_ts + 100000.0, "r2": 500.0}), encoding="utf-8"
|
|
)
|
|
|
|
stats.record_open("r3")
|
|
await stats.save_stats(force=True)
|
|
|
|
data = json.loads(stats_file.read_text(encoding="utf-8"))
|
|
# r2 from the other process survives; r1 keeps the newer disk timestamp;
|
|
# r3 from this process is added
|
|
assert data["r1"] == first_ts + 100000.0
|
|
assert data["r2"] == 500.0
|
|
assert isinstance(data["r3"], float)
|
|
await _finalize(tasks)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_opened_map_returns_copy(tmp_path, monkeypatch):
|
|
stats, tasks, _ = _prepare(tmp_path, monkeypatch)
|
|
stats.record_open("r1")
|
|
|
|
opened_map = stats.get_opened_map()
|
|
opened_map["injected"] = 1.0
|
|
assert "injected" not in stats.get_opened_map()
|
|
await _finalize(tasks)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_missing_stats_file_returns_empty_map(tmp_path, monkeypatch):
|
|
stats, tasks, _ = _prepare(tmp_path, monkeypatch)
|
|
assert stats.get_opened_map() == {}
|
|
await _finalize(tasks)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_stats_skips_when_not_dirty(tmp_path, monkeypatch):
|
|
stats, tasks, settings_dir = _prepare(tmp_path, monkeypatch)
|
|
stats_file = settings_dir / "stats" / RecipeOpenStats.STATS_FILENAME
|
|
|
|
assert await stats.save_stats() is False
|
|
assert not stats_file.exists()
|
|
await _finalize(tasks)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_load_ignores_corrupt_file(tmp_path, monkeypatch):
|
|
settings_dir = tmp_path / "settings"
|
|
settings_dir.mkdir(parents=True, exist_ok=True)
|
|
stats_file = settings_dir / "stats" / RecipeOpenStats.STATS_FILENAME
|
|
stats_file.parent.mkdir(parents=True, exist_ok=True)
|
|
stats_file.write_text("{not valid json", encoding="utf-8")
|
|
|
|
monkeypatch.setattr(
|
|
stats_module, "get_settings_dir", lambda create=True: str(settings_dir)
|
|
)
|
|
RecipeOpenStats._instance = None
|
|
stats = RecipeOpenStats()
|
|
assert stats.get_opened_map() == {}
|