- event bus systen - all components are independent - preparation for web frontend
105 lines
3.5 KiB
Python
105 lines
3.5 KiB
Python
"""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
|