LetItCrash (let_it_crash v0.4.0)
View SourceA testing library for crash recovery and OTP supervision behavior.
LetItCrash helps you test that your GenServers and supervised processes
recover correctly after crashes, embracing Elixir's "let it crash" philosophy.
Usage
use LetItCrash
test "genserver recovers after crash" do
{:ok, pid} = MyGenServer.start_link([])
LetItCrash.crash(pid)
assert LetItCrash.recovered?(MyGenServer)
endCrash Functions
The library provides a crash/2 function that allows you to specify the type of exit signal.
It follows the same convention as Process.exit/2 for consistency and piping support:
crash(pid)- Sends a:shutdownexit signal (default, can be trapped)crash(pid, :shutdown)- Explicitly sends:shutdownsignalcrash(pid, :kill)- Sends a:killexit signal (cannot be trapped, guarantees termination)
Use :kill when testing processes that trap exits, such as GenServers that
need to perform cleanup operations.
Summary
Functions
Imports LetItCrash testing functions into the current module.
Asserts that a process properly cleans up its Registry entries on crash and recovery.
Crashes a process by sending it an exit signal.
Checks if a registered process has recovered (restarted) after a crash.
Tests that a process can recover from a crash by executing a test function before and after the crash.
Verifies that ETS table entries are properly cleaned up when a process crashes.
Waits for a registered process to exist and be alive.
Functions
Imports LetItCrash testing functions into the current module.
Asserts that a process properly cleans up its Registry entries on crash and recovery.
This function verifies that:
- The old Registry entry is removed when the process crashes
- A new Registry entry is created when the process recovers
- The new entry points to the new PID
Parameters
registry- The Registry module to monitorprocess_name- The registered name/key of the processopts- Options for the verification:timeout- Maximum time to wait for cleanup and re-registration (default: 2000ms)
Examples
test "process cleans up registry on restart" do
{:ok, _pid} = MyServer.start_link(name: :my_server)
Registry.register(MyApp.Registry, :my_server, %{status: :active})
LetItCrash.crash(:my_server)
LetItCrash.assert_clean_registry(MyApp.Registry, :my_server)
end
Crashes a process by sending it an exit signal.
Follows the same convention as Process.exit/2, with the process as the first argument
to enable easy piping.
Parameters
process- A PID or registered process name to crashtype- The type of exit signal::shutdown(default) or:kill
The :shutdown signal can be trapped by processes with Process.flag(:trap_exit, true),
while :kill cannot be trapped and guarantees termination.
Examples
# Default :shutdown signal:
{:ok, pid} = MyGenServer.start_link([])
LetItCrash.crash(pid)
# Explicitly specifying :shutdown:
LetItCrash.crash(pid, :shutdown)
# Piping support:
Process.whereis(:my_process)
|> LetItCrash.crash(:kill)
# For processes with trap_exit, use :kill:
defmodule ScoreCoordinator do
use GenServer
def init(_) do
Process.flag(:trap_exit, true)
{:ok, %{}}
end
end
{:ok, pid} = ScoreCoordinator.start_link([])
LetItCrash.crash(pid, :kill) # Guarantees termination
Checks if a registered process has recovered (restarted) after a crash.
This function works by comparing the current PID of a registered process with a previously stored PID. If they differ, it means the process was restarted.
Parameters
process_name- The registered name of the process to checkoriginal_pid- The PID before the crash (optional, will be retrieved if not provided)opts- Options for recovery checking:timeout- Maximum time to wait for recovery (default: 1000ms):interval- Polling interval (default: 50ms)
Examples
test "process recovers after crash" do
original_pid = Process.whereis(MyGenServer)
LetItCrash.crash(MyGenServer)
assert LetItCrash.recovered?(MyGenServer, original_pid)
end
Tests that a process can recover from a crash by executing a test function before and after the crash.
Parameters
process- PID or registered name of the process to testtest_fn- Function to execute before and after crashopts- Options for the test:timeout- Maximum time to wait for recovery (default: 1000ms)
Examples
test "maintains state after restart" do
LetItCrash.test_restart(MyStatefulServer, fn ->
assert MyStatefulServer.get_count() == 0
MyStatefulServer.increment()
assert MyStatefulServer.get_count() == 1
end)
end
Verifies that ETS table entries are properly cleaned up when a process crashes.
This function monitors specific ETS table entries and ensures they are cleaned up appropriately during process restart.
Parameters
table- The ETS table name or reference to monitorkey- The key to monitor in the ETS tableopts- Options for the verification:timeout- Maximum time to wait for cleanup (default: 1000ms):expect_cleanup- Whether to expect the entry to be cleaned up (default: true):expect_recreate- Whether to expect the entry to be recreated (default: false)
Examples
test "cleans up ETS entries on crash" do
:ets.insert(:my_cache, {:server_data, "important"})
LetItCrash.crash(:my_server)
LetItCrash.verify_ets_cleanup(:my_cache, :server_data)
end
test "recreates ETS entries after recovery" do
LetItCrash.crash(:my_server)
LetItCrash.verify_ets_cleanup(:my_cache, :server_data,
expect_cleanup: true, expect_recreate: true)
end
Waits for a registered process to exist and be alive.
This function is useful in test setup when you need to ensure a process is available before interacting with it, particularly after starting supervisors or during async initialization.
Parameters
process_name- The registered name of the process to wait foropts- Options for waiting:timeout- Maximum time to wait (default: 1000ms):interval- Polling interval (default: 50ms)
Returns
:ok- Process exists and is alive{:error, :timeout}- Process did not appear within timeout
Examples
test "worker is available after supervisor starts" do
{:ok, _sup} = MySupervisor.start_link()
# Wait for the worker to be ready
:ok = LetItCrash.wait_for_process(:my_worker)
# Now safe to interact with it
assert MyWorker.get_status() == :ready
end
# With custom timeout for slow-starting processes
:ok = LetItCrash.wait_for_process(:heavy_worker, timeout: 5000)