A behavior module for GenStateMachine with virtual time support.
This module wraps GenStateMachine and provides a send_after/3 function that can
work with either real time (production) or virtual time (testing).
Example
defmodule MyStateMachine do
use VirtualTimeGenStateMachine, callback_mode: :handle_event_function
def start_link(opts) do
GenStateMachine.start_link(__MODULE__, :off, opts)
end
@impl true
def init(_) do
{:ok, :off, %{count: 0}}
end
@impl true
def handle_event(:cast, :flip, :off, data) do
schedule_timer(100)
{:next_state, :on, %{data | count: data.count + 1}}
end
@impl true
def handle_event(:cast, :flip, :on, data) do
{:next_state, :off, data}
end
@impl true
def handle_event(:info, :timeout, _state, data) do
{:keep_state, %{data | timeout_fired: true}}
end
defp schedule_timer(delay) do
VirtualTimeGenStateMachine.send_after(self(), :timeout, delay)
end
endTesting with Virtual Time
test "state machine with timers" do
{:ok, clock} = VirtualClock.start_link()
VirtualTimeGenStateMachine.set_virtual_clock(clock)
{:ok, server} = MyStateMachine.start_link([])
# Trigger state transition
GenStateMachine.cast(server, :flip)
# Advance virtual time - timer fires instantly
VirtualClock.advance(clock, 100)
# Check that timeout fired
assert get_timeout_fired(server) == true
end
Summary
Functions
Makes a synchronous call to a state machine.
Cancels a timer created with send_after/3. Uses the appropriate backend based on the current process configuration.
Sends an asynchronous cast to a state machine.
Gets the current time backend.
Sends a message to a process after a delay in milliseconds. Uses the appropriate backend based on the current process configuration.
Sends a message immediately in virtual time.
Sets the virtual clock for the current process. All child processes will inherit this setting.
Sets the virtual clock for the current process without emitting warnings.
Sleeps for the specified duration in milliseconds. Uses the appropriate backend based on the current process configuration.
Starts a GenStateMachine without linking.
Starts a GenStateMachine with virtual time support.
Stops a state machine.
Uses real time (default behavior).
Uses real time without emitting warnings.
Functions
Makes a synchronous call to a state machine.
Cancels a timer created with send_after/3. Uses the appropriate backend based on the current process configuration.
Sends an asynchronous cast to a state machine.
Gets the current time backend.
Sends a message to a process after a delay in milliseconds. Uses the appropriate backend based on the current process configuration.
Sends a message immediately in virtual time.
With virtual time, this schedules the message for the current virtual time, ensuring it gets processed in the next event cycle. With real time, this sends the message immediately.
This is useful for triggering immediate responses or state changes within the virtual time simulation.
Examples
# Send immediate message to self
VirtualTimeGenStateMachine.send_immediately(self(), :process_now)
# Send immediate message to another process
VirtualTimeGenStateMachine.send_immediately(other_pid, {:urgent, data})
Sets the virtual clock for the current process. All child processes will inherit this setting.
Sets the virtual clock for the current process without emitting warnings.
Use this when you intentionally want global virtual clock behavior and understand the implications. The explanation message should describe why global clock is needed.
Example
iex> {:ok, clock} = VirtualClock.start_link()
iex> VirtualTimeGenStateMachine.set_virtual_clock(clock, :i_know_what_i_am_doing, "coordinated simulation")
VirtualTimeBackend
Sleeps for the specified duration in milliseconds. Uses the appropriate backend based on the current process configuration.
Starts a GenStateMachine without linking.
Starts a GenStateMachine with virtual time support.
Returns {:ok, pid, backend} where backend is the time backend to use. Store the backend in your process state for optimal performance.
Stops a state machine.
Uses real time (default behavior).
Uses real time without emitting warnings.
Use this when you intentionally want global real time behavior and understand the implications. The explanation message should describe why global real time is needed.
Example
iex> VirtualTimeGenStateMachine.use_real_time(:i_know_what_i_am_doing, "production mode")
RealTimeBackend