Distributed test cluster management for Elixir applications.
ClusterTest provides automated lifecycle management for distributed test clusters, eliminating manual server management and ensuring reliable testing in distributed Erlang/Elixir environments.
- Automated cluster lifecycle management (start, stop, restart)
- Dynamic node provisioning with configurable cluster sizes
- Health monitoring and validation
- Code synchronization across cluster nodes
- Environment isolation and cleanup
- Comprehensive error diagnostics
- Mix task integration for easy command-line usage
- WSL Ubuntu 24.04 compatibility with network fixes
Add cluster_test to your list of dependencies in mix.exs:
def deps do
[
{:cluster_test, "~> 0.0.1"}
]
endchildren = [
ClusterTest
]
Supervisor.start_link(children, strategy: :one_for_one)# Check if environment is ready for distributed testing
mix cluster_test preflight
# Start a test cluster
mix cluster_test start
# Run distributed tests
mix cluster_test run
# Check cluster health
mix cluster_test health
# Clean up
mix cluster_test clean# Start cluster programmatically
{:ok, nodes} = ClusterTest.start_cluster(node_count: 3)
# Get cluster status
{:ok, status} = ClusterTest.get_status()
# Run health check
{:ok, health} = ClusterTest.health_check()
# Stop cluster
:ok = ClusterTest.stop_cluster()Configure in your application's config.exs:
config :cluster_test, :distributed_testing,
http_port_base: 4200,
dist_port_base: 9200,
default_cluster_size: 2,
startup_timeout: 30_000:http_port_base- Starting port for HTTP servers (default: 4200):dist_port_base- Starting port for Erlang distribution (default: 9200):default_cluster_size- Default number of nodes (default: 2):startup_timeout- Cluster startup timeout in ms (default: 30000)
Start a distributed test cluster.
# Start with default size (2 nodes)
mix cluster_test start
# Start with specific size
mix cluster_test start --size 4Stop the test cluster and clean up all nodes.
mix cluster_test stopRestart the test cluster (stop + start).
mix cluster_test restart --size 3Show current cluster status and running processes.
mix cluster_test statusRun comprehensive health checks on the cluster.
mix cluster_test healthClean up all test artifacts and processes.
mix cluster_test cleanRun the full test cycle: start cluster → run tests → cleanup.
mix cluster_test run --size 3Run pre-flight environment checks.
mix cluster_test preflightShow logs from test nodes.
# All nodes
mix cluster_test logs
# Specific node
mix cluster_test logs test_node1# Start cluster with options
{:ok, nodes} = ClusterTest.start_cluster(node_count: 4)
# Stop cluster
:ok = ClusterTest.stop_cluster()# Get cluster status
{:ok, status} = ClusterTest.get_status()
status.overall #=> :running
status.nodes #=> %{node1: %{healthy: true, ...}, ...}
# Run health check
{:ok, results} = ClusterTest.health_check()
results.all_passed #=> true
results.checks #=> [...]# Clean all artifacts
:ok = ClusterTest.clean_all()
# Get logs
{:ok, logs} = ClusterTest.get_logs()
{:ok, logs} = ClusterTest.get_logs("test_node1")# Check prerequisites
case ClusterTest.check_prerequisites() do
:ok -> IO.puts("Environment ready!")
{:error, issues} -> IO.inspect(issues)
end
# Find available ports
{:ok, ports} = ClusterTest.find_available_ports(3)
# => {:ok, [{4200, 9200}, {4201, 9201}, {4202, 9202}]}
# Get hostname for cluster
{:ok, hostname} = ClusterTest.get_cluster_hostname()
# => {:ok, "127.0.0.1"}Mark your tests to run only with real cluster nodes:
defmodule MyDistributedTest do
use ExUnit.Case
@tag :real_nodes
test "distributed functionality" do
# This test runs only when cluster is active
{:ok, nodes} = ClusterTest.get_status()
assert nodes.overall == :running
# Your distributed test logic here
end
endRun tests with the cluster:
# Start cluster and run distributed tests
mix cluster_test run
# Or manually
mix cluster_test start
mix test --only real_nodes
mix cluster_test stopClusterTest includes fixes for common WSL Ubuntu 24.04 issues:
# Check environment
mix cluster_test preflight
# Common fixes
epmd -daemon
sudo systemctl restart systemd-resolved# Check what's using ports
netstat -tulpn | grep 4200
# Clean up processes
mix cluster_test clean
pkill -f test_node# Check EPMD
epmd -names
# Test connectivity
ping localhost
ping 127.0.0.1# Monitor cluster resource usage
mix cluster_test healthClusterTest provides comprehensive error diagnosis:
# Get diagnostic information
diagnosis = ClusterTest.diagnose_startup_failure(reason)
IO.puts(diagnosis.problem)
Enum.each(diagnosis.solutions, &IO.puts(" • #{&1}"))defmodule MyApp.ClusterTest do
use ExUnit.Case
@tag :real_nodes
test "nodes can communicate" do
{:ok, status} = ClusterTest.get_status()
assert status.overall == :running
assert map_size(status.nodes) >= 2
# Test node communication
nodes = Map.values(status.nodes)
for node <- nodes do
assert node.healthy == true
end
end
end# config/test.exs
config :cluster_test, :distributed_testing,
http_port_base: 4300,
dist_port_base: 9300,
default_cluster_size: 3,
startup_timeout: 45_000# .github/workflows/test.yml
- name: Run distributed tests
run: |
epmd -daemon
mix cluster_test run --size 2ClusterTest consists of several key components:
- Manager - Main GenServer coordinating cluster lifecycle
- PortManager - Dynamic port allocation and cleanup
- HostnameResolver - WSL-aware hostname resolution
- HealthChecker - Comprehensive cluster health validation
- Diagnostics - Error analysis and troubleshooting
- ExecWrapper - Process management via erlexec
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.