GenServerVirtualTimeMigrate

Copy Markdown View Source

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

PackageWhat it is forAdd it when
gen_server_virtual_timevirtual clock, GenServer/gen_statem wrappersyou test code with timers
gen_server_virtual_time_migrate (this one)rewrite code for the virtual clock, rename deprecated modulesyou already have servers, or code that predates 1.1
actor_simulationactor-system DSL, statistics, sequence diagramsyou model parts of a system talking to each other
actor_simulation_codegengenerate runnable projects in six runtimesyou 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

BeforeAfter
use GenServeruse VirtualTimeGenServer
use GenStateMachineuse 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/2 on a pid, a variable, or a module that stays a plain GenServer — the call may still be right, the tool just cannot prove it;
  • :erlang.send_after/3, whose delay is its first argument while VirtualTimeGenServer.send_after/3 wants 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_statem timeouts, which the virtual-time state machine handles through its own options — see VirtualTimeGenStateMachine.

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
BeforeAfter
ActorSimulation.CAFGenerator.generate(sim)ActorSimulation.Codegen.CAF.generate(sim)
alias ActorSimulation.PonyGeneratoralias 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.