feat: add simulation clocks and VM transitions
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
"""Simulation clock behavior."""
|
||||
|
||||
import asyncio
|
||||
from datetime import UTC, datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.simulation.clock import AcceleratedClock, ManualClock
|
||||
|
||||
|
||||
async def test_manual_clock_releases_sleep_only_after_advance() -> None:
|
||||
clock = ManualClock(datetime(2026, 1, 1, tzinfo=UTC))
|
||||
sleeper = asyncio.create_task(clock.sleep(10))
|
||||
await asyncio.sleep(0)
|
||||
assert not sleeper.done()
|
||||
|
||||
await clock.advance(9)
|
||||
assert not sleeper.done()
|
||||
await clock.advance(1)
|
||||
await sleeper
|
||||
assert await clock.now() == datetime(2026, 1, 1, 0, 0, 10, tzinfo=UTC)
|
||||
|
||||
|
||||
def test_clocks_reject_invalid_configuration() -> None:
|
||||
with pytest.raises(ValueError):
|
||||
AcceleratedClock(0)
|
||||
with pytest.raises(ValueError):
|
||||
ManualClock(datetime(2026, 1, 1))
|
||||
@@ -0,0 +1,46 @@
|
||||
"""VM state-machine and deterministic fault properties."""
|
||||
|
||||
import pytest
|
||||
from hypothesis import given
|
||||
from hypothesis import strategies as st
|
||||
|
||||
from app.simulation.scenarios import FaultContext, FaultRule, matches
|
||||
from app.simulation.transitions import InvalidTransitionError, VmState, plan_transition
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("state", "operation", "final"),
|
||||
[
|
||||
(VmState.STOPPED, "start", VmState.RUNNING),
|
||||
(VmState.RUNNING, "stop", VmState.STOPPED),
|
||||
(VmState.RUNNING, "pause", VmState.PAUSED),
|
||||
(VmState.PAUSED, "resume", VmState.RUNNING),
|
||||
(VmState.RUNNING, "snapshot", VmState.RUNNING),
|
||||
(VmState.STOPPED, "migrate", VmState.STOPPED),
|
||||
],
|
||||
)
|
||||
def test_valid_transitions(state: VmState, operation: str, final: VmState) -> None:
|
||||
transition = plan_transition(state, operation)
|
||||
assert transition.before is state
|
||||
assert transition.after is final
|
||||
assert transition.intermediate is not state
|
||||
|
||||
|
||||
@given(st.sampled_from(tuple(VmState)), st.text(min_size=1, max_size=12))
|
||||
def test_transition_result_is_declared_or_rejected(state: VmState, operation: str) -> None:
|
||||
try:
|
||||
transition = plan_transition(state, operation)
|
||||
except InvalidTransitionError:
|
||||
return
|
||||
assert transition.before is state
|
||||
|
||||
|
||||
def test_fault_evaluation_is_seeded_and_filtered() -> None:
|
||||
context = FaultContext("POST", "/nodes/pve1/qemu/100/status/start", node="pve1")
|
||||
certain = FaultRule("task-failure", method="POST", node="pve1")
|
||||
impossible = FaultRule("task-failure", probability=0)
|
||||
|
||||
assert matches(certain, context, seed=42)
|
||||
assert not matches(impossible, context, seed=42)
|
||||
probabilistic = FaultRule("task-failure", probability=0.5)
|
||||
assert matches(probabilistic, context, 42) == matches(probabilistic, context, 42)
|
||||
Reference in New Issue
Block a user