Skip to content

Commit cdba1d7

Browse files
committed
VirtualTimeGenStateMachine
1 parent d181fe2 commit cdba1d7

5 files changed

Lines changed: 67 additions & 4 deletions

File tree

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,52 @@ test "100 seconds completes instantly" do
6666
end
6767
```
6868

69+
### Test state machines with virtual time
70+
71+
```elixir
72+
defmodule SwitchStateMachine do
73+
use VirtualTimeGenStateMachine, callback_mode: :handle_event_function
74+
75+
def start_link(opts) do
76+
GenStateMachine.start_link(__MODULE__, :off, opts)
77+
end
78+
79+
def init(_) do
80+
{:ok, :off, %{flip_count: 0}}
81+
end
82+
83+
def handle_event(:cast, :flip, :off, data) do
84+
# Schedule timeout for 100ms
85+
VirtualTimeGenStateMachine.send_after(self(), :timeout, 100)
86+
{:next_state, :on, %{data | flip_count: data.flip_count + 1}}
87+
end
88+
89+
def handle_event(:cast, :flip, :on, data) do
90+
{:next_state, :off, data}
91+
end
92+
93+
def handle_event(:info, :timeout, state, data) do
94+
{:keep_state, %{data | timeout_fired: true}}
95+
end
96+
end
97+
98+
test "state machine transitions and timers" do
99+
{:ok, clock} = VirtualClock.start_link()
100+
VirtualTimeGenStateMachine.set_virtual_clock(clock)
101+
102+
{:ok, sm} = SwitchStateMachine.start_link([])
103+
104+
# Trigger transition and schedule timeout
105+
GenStateMachine.cast(sm, :flip)
106+
107+
# Advance virtual time - timeout fires instantly
108+
VirtualClock.advance(clock, 100) # ~10ms real time ⚡
109+
110+
# Check timeout fired
111+
assert get_state(sm).timeout_fired == true
112+
end
113+
```
114+
69115
### Simulate message-passing systems
70116

71117
```elixir
@@ -277,6 +323,12 @@ end
277323
- All standard callbacks: `handle_call`, `handle_cast`, `handle_info`
278324
- Fast: simulate hours in seconds
279325

326+
**VirtualTimeGenStateMachine** - Test state machines with virtual time
327+
328+
- Drop-in replacement: `use VirtualTimeGenStateMachine` instead of `use GenStateMachine`
329+
- All standard callbacks: `handle_event`, state transitions, timeouts
330+
- Fast: simulate complex state transitions instantly
331+
280332
**Actor Simulation DSL** - Prototype distributed systems
281333

282334
- Pattern matching: declarative message handlers
@@ -310,6 +362,11 @@ use VirtualTimeGenServer
310362
VirtualTimeGenServer.set_virtual_clock(clock)
311363
VirtualTimeGenServer.send_after(pid, msg, delay)
312364

365+
# Virtual Time GenStateMachine
366+
use VirtualTimeGenStateMachine, callback_mode: :handle_event_function
367+
VirtualTimeGenStateMachine.set_virtual_clock(clock)
368+
VirtualTimeGenStateMachine.send_after(pid, msg, delay)
369+
313370
# Actor Simulation (import for clean DSL)
314371
import ActorSimulation
315372

lib/actor_simulation/omnetpp_generator.ex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ defmodule ActorSimulation.OMNeTPPGenerator do
438438
# Use modern CMake targets if available
439439
target_link_libraries(#{network_name} PRIVATE
440440
OmnetPP::main
441+
OmnetPP::cmdenv
441442
OmnetPP::envir
442443
OmnetPP::sim
443444
)
@@ -452,6 +453,10 @@ defmodule ActorSimulation.OMNeTPPGenerator do
452453
${OMNETPP_INCLUDE_DIRS}
453454
${CMAKE_CURRENT_SOURCE_DIR}
454455
)
456+
# Ensure shared libraries are linked properly
457+
target_link_options(#{network_name} PRIVATE
458+
-Wl,--no-as-needed
459+
)
455460
endif()
456461
"""
457462
end

lib/virtual_time_gen_state_machine.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule VirtualTimeGenStateMachine do
1111
use VirtualTimeGenStateMachine, callback_mode: :handle_event_function
1212
1313
def start_link(opts) do
14-
VirtualTimeGenStateMachine.start_link(__MODULE__, :off, opts)
14+
GenStateMachine.start_link(__MODULE__, :off, opts)
1515
end
1616
1717
@impl true

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ defmodule GenServerVirtualTime.MixProject do
9191

9292
defp description do
9393
"""
94-
Virtual time extension to `GenServer` allowing testing time-based
94+
Virtual time extension to `GenServer` and `GenStateMachine` allowing testing time-based
9595
actor systems orders of magnitude faster than in wallclock-time.
9696
Includes actor simulation DSL with statistics, tracing, and code generation into other
9797
Actor Model implementations in C++, Pony, Go, Rust, Java.
@@ -150,6 +150,7 @@ defmodule GenServerVirtualTime.MixProject do
150150
Core: [
151151
VirtualClock,
152152
VirtualTimeGenServer,
153+
VirtualTimeGenStateMachine,
153154
TimeBackend,
154155
VirtualTimeBackend,
155156
RealTimeBackend

test/virtual_time_gen_state_machine_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ defmodule VirtualTimeGenStateMachineTest do
5454
defmodule DoorSM do
5555
use VirtualTimeGenStateMachine, callback_mode: :state_functions
5656

57-
def start_link() do
57+
def start_link do
5858
GenStateMachine.start_link(__MODULE__, nil, [])
5959
end
6060

@@ -121,7 +121,7 @@ defmodule VirtualTimeGenStateMachineTest do
121121
defmodule LightSM do
122122
use VirtualTimeGenStateMachine, callback_mode: [:handle_event_function, :state_enter]
123123

124-
def start_link() do
124+
def start_link do
125125
GenStateMachine.start_link(__MODULE__, nil, [])
126126
end
127127

0 commit comments

Comments
 (0)