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.