Compile-time AST rewriter that converts vanilla OTP calls into their
Lockstep.* equivalents inside a ctest body. Lets test bodies
read like ordinary Elixir without sacrificing controlled scheduling.
Enabled with use Lockstep.Test, rewrite: true. With rewrite: true
the linter is also disabled (since the rewritten body is correct by
construction).
Mappings
vanilla rewritten
------- ---------
GenServer.call(s, m) Lockstep.GenServer.call(s, m)
GenServer.cast(s, m) Lockstep.GenServer.cast(s, m)
GenServer.start_link(M, A) Lockstep.GenServer.start_link(M, A)
Task.async(fn -> ... end) Lockstep.Task.async(fn -> ... end)
Task.await(t) Lockstep.Task.await(t)
Task.await_many(ts) Lockstep.Task.await_many(ts)
spawn(fun) Lockstep.spawn(fun)
spawn_link(fun) Lockstep.spawn_link(fun)
send(p, m) Lockstep.send(p, m)
Kernel.send(p, m) Lockstep.send(p, m)
:erlang.send(p, m) Lockstep.send(p, m)
Process.send_after(p, m, ms) Lockstep.send_after(p, m, ms)
Process.cancel_timer(ref) Lockstep.cancel_timer(ref)
Process.monitor(p) Lockstep.monitor(p)
Process.demonitor(ref[, opts]) Lockstep.demonitor(ref[, opts])
Process.alive?(p) Lockstep.alive?(p)
Process.sleep(ms) Lockstep.sleep(ms)
Process.link(p) Lockstep.link(p)
Process.unlink(p) Lockstep.unlink(p)
Process.flag(:trap_exit, b) Lockstep.flag(:trap_exit, b)
receive do clauses end rewritten to Lockstep.recv_first/1Limitations
- Only the body of
ctestis rewritten, not helper functions defined elsewhere. Inline race code into the test body, or call Lockstep wrappers directly in helpers. receive ... after t -> ... end(bare receive with timeout) is not yet rewritten -- bare receives withoutafterare.:erlang.spawn/3(the 3-arg MFA form) is not rewritten.
Functions defined locally that happen to be named send/2 etc.
will be rewritten too. Suppress by qualifying the call (e.g.
MyMod.send(...) is left alone).
Summary
Functions
Walk ast and return the rewritten AST.