Two dev-only rewrite tasks: move existing GenServer code onto the virtual
clock, and move a project off the module names that were renamed in 1.1.
Both read your tree, show exactly what they would change, and write only when you ask.
$ mix virtual_time.migrate
lib/my_app/counter.ex
line 2: use GenServer
1 file(s) would change. Re-run with --write to apply.
The family
| Package | What it is for | Add it when |
|---|---|---|
gen_server_virtual_time | virtual clock, GenServer/gen_statem wrappers | you test code with timers |
gen_server_virtual_time_migrate (this one) | rewrite code for the virtual clock, rename deprecated modules | you already have servers, or code that predates 1.1 |
actor_simulation | actor-system DSL, statistics, sequence diagrams | you model parts of a system talking to each other |
actor_simulation_codegen | generate runnable projects in six runtimes | you want the same model in another language |
This package is standalone: it only rewrites text, so it needs nothing at
runtime. The rewritten code needs gen_server_virtual_time.
Install
def deps do
[
{:gen_server_virtual_time, "~> 1.1.1"},
{:gen_server_virtual_time_migrate, "~> 1.0.0", only: :dev}
]
endUse
mix virtual_time.migrate # report only
mix virtual_time.migrate --write # apply
mix virtual_time.migrate --check # exit 1 if anything would change
mix virtual_time.migrate --paths lib --paths test # defaults to lib and test
A rewrite that would not parse aborts the run before anything is written, so a
--write either produces a compiling tree or leaves it alone.
What it rewrites
| Before | After |
|---|---|
use GenServer | use VirtualTimeGenServer |
use GenStateMachine | use VirtualTimeGenStateMachine |
Process.send_after(pid, :tick, 1_000) | VirtualTimeGenServer.send_after(pid, :tick, 1_000) |
Process.sleep(10) | VirtualTimeGenServer.sleep(10) |
:timer.sleep(10) | VirtualTimeGenServer.sleep(10) |
GenServer.call(MyApp.Counter, :get) | VirtualTimeGenServer.call(MyApp.Counter, :get) |
The time calls are only rewritten inside a module that is itself converted — that is what puts the module on the virtual clock.
What it leaves for you
The tool never guesses, and it says what it skipped:
GenServer.call/2on a pid, a variable, or a module that stays a plainGenServer— the call may still be right, the tool just cannot prove it;:erlang.send_after/3, whose delay is its first argument whileVirtualTimeGenServer.send_after/3wants it last — swapping arguments is not a token replacement, so the line is reported with the fix spelled out;- anything outside a converted module;
:gen_statemtimeouts, which the virtual-time state machine handles through its own options — seeVirtualTimeGenStateMachine.
Comments, strings and formatting elsewhere in the file are untouched: the
rewrite is a token replacement, so git diff shows only the lines that changed.
The other task: deprecated generator names
ActorSimulation.CAFGenerator and its five siblings became
ActorSimulation.Codegen.* in 1.1. The old names still work — they forward, and
warn — so this is optional: it is the difference between a build with
deprecation warnings and one without.
mix virtual_time.upgrade # report only
mix virtual_time.upgrade --write # apply
mix virtual_time.upgrade --check # exit 1 if anything would change
| Before | After |
|---|---|
ActorSimulation.CAFGenerator.generate(sim) | ActorSimulation.Codegen.CAF.generate(sim) |
alias ActorSimulation.PonyGenerator | alias ActorSimulation.Codegen.Pony |
PonyGenerator.generate(sim) (under that alias) | Pony.generate(sim) |
It reports instead of guessing when an alias uses as:, when the name it would
introduce is already in use in that file, and when a bare short name has no
alias it can point at.
Compatibility
Input is parsed with the standard library (Code.string_to_quoted/2), so the
migrator needs the same Elixir as the project it runs in. The virtual-time
packages themselves keep working on Elixir 1.19 and OTP 28 as well.