GenServerVirtualTimeMigrate

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.

Hex.pm Documentation

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}
  ]
end

Use

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:

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.