"""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