Full rearchitecture using Claude
- event bus systen - all components are independent - preparation for web frontend
This commit is contained in:
104
python-backend/tests/test_scenarios.py
Normal file
104
python-backend/tests/test_scenarios.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""Run every file in ``scenarios/`` as a test.
|
||||
|
||||
Same driver, same verbs, same code path as the interactive simulator - only the clock
|
||||
differs, so a scenario that takes half a minute by hand runs in microseconds here.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from musicmouse.config import load_config
|
||||
from musicmouse.simulator.driver import ExpectationError, ScriptError
|
||||
from musicmouse.simulator.harness import Simulation, build_simulation
|
||||
from musicmouse.simulator.script import run_script_file
|
||||
from tests.conftest import VALID_CONFIG, write_config
|
||||
|
||||
SCENARIO_DIR = Path(__file__).resolve().parents[1] / "scenarios"
|
||||
SCENARIOS = sorted(SCENARIO_DIR.glob("*.txt"))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def sim(config_dir: Path) -> AsyncIterator[Simulation]:
|
||||
config = load_config(write_config(config_dir, VALID_CONFIG))
|
||||
simulation = await build_simulation(config)
|
||||
try:
|
||||
yield simulation
|
||||
finally:
|
||||
await simulation.aclose()
|
||||
|
||||
|
||||
def test_scenario_files_exist() -> None:
|
||||
assert SCENARIOS, f"no scenario files found in {SCENARIO_DIR}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("scenario", SCENARIOS, ids=lambda p: p.stem)
|
||||
async def test_scenario(sim: Simulation, scenario: Path) -> None:
|
||||
await run_script_file(sim, scenario)
|
||||
|
||||
|
||||
# --------------------------------------------------------------- the script parser
|
||||
|
||||
|
||||
async def test_comments_and_blank_lines_are_ignored(sim: Simulation) -> None:
|
||||
await sim.driver.run_script("# just a comment\n\n \nplace fuchs # trailing\n")
|
||||
sim.driver.check("figure", "fuchs")
|
||||
|
||||
|
||||
async def test_a_failing_expectation_names_the_line(sim: Simulation) -> None:
|
||||
with pytest.raises(ExpectationError) as excinfo:
|
||||
await sim.driver.run_script("place fuchs\nexpect track 7\n")
|
||||
|
||||
message = str(excinfo.value)
|
||||
assert "line 2" in message
|
||||
assert "expected track to be '7', but it is '0'" in message
|
||||
|
||||
|
||||
async def test_an_unknown_verb_is_reported_with_its_line(sim: Simulation) -> None:
|
||||
with pytest.raises(ScriptError, match="line 1"):
|
||||
await sim.driver.run_script("frobnicate the widget\n")
|
||||
|
||||
|
||||
async def test_an_unknown_figure_lists_the_configured_ones(sim: Simulation) -> None:
|
||||
with pytest.raises(ScriptError, match="configured: eule, fuchs"):
|
||||
await sim.driver.run_script("place giraffe\n")
|
||||
|
||||
|
||||
async def test_an_unknown_property_lists_the_valid_ones(sim: Simulation) -> None:
|
||||
with pytest.raises(ScriptError, match="unknown property"):
|
||||
await sim.driver.run_script("expect loudness 5\n")
|
||||
|
||||
|
||||
async def test_an_unknown_touch_button_lists_the_valid_ones(sim: Simulation) -> None:
|
||||
with pytest.raises(ScriptError, match="left_foot, right_foot"):
|
||||
await sim.driver.run_script("touch nose\n")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("text", "seconds"),
|
||||
[("2", 2.0), ("2s", 2.0), ("500ms", 0.5), ("1m", 60.0), ("0.5s", 0.5)],
|
||||
)
|
||||
async def test_duration_formats(text: str, seconds: float) -> None:
|
||||
from musicmouse.simulator.driver import Duration
|
||||
|
||||
assert Duration.parse(text).seconds == pytest.approx(seconds)
|
||||
|
||||
|
||||
async def test_a_bad_duration_is_reported() -> None:
|
||||
from musicmouse.simulator.driver import Duration
|
||||
|
||||
with pytest.raises(ScriptError, match="is not a duration"):
|
||||
Duration.parse("soon")
|
||||
|
||||
|
||||
async def test_status_and_leds_produce_output(sim: Simulation) -> None:
|
||||
await sim.driver.place("fuchs")
|
||||
|
||||
status = await sim.driver.execute("status")
|
||||
leds = await sim.driver.execute("leds")
|
||||
|
||||
assert status is not None and "fuchs" in status and "playing" in status
|
||||
assert leds is not None and "ring" in leds and "shelf" in leds
|
||||
Reference in New Issue
Block a user