Skip to content

nshkrdotcom/cluster_test

Repository files navigation

ClusterTest

Hex.pm Documentation

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.

Features

  • 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

Installation

Add cluster_test to your list of dependencies in mix.exs:

def deps do
  [
    {:cluster_test, "~> 0.0.1"}
  ]
end

Quick Start

1. Add to your application supervision tree

children = [
  ClusterTest
]

Supervisor.start_link(children, strategy: :one_for_one)

2. Use the Mix tasks

# 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

3. Programmatic usage

# 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()

Configuration

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

Configuration Options

  • :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)

Mix Tasks

mix cluster_test start

Start a distributed test cluster.

# Start with default size (2 nodes)
mix cluster_test start

# Start with specific size
mix cluster_test start --size 4

mix cluster_test stop

Stop the test cluster and clean up all nodes.

mix cluster_test stop

mix cluster_test restart

Restart the test cluster (stop + start).

mix cluster_test restart --size 3

mix cluster_test status

Show current cluster status and running processes.

mix cluster_test status

mix cluster_test health

Run comprehensive health checks on the cluster.

mix cluster_test health

mix cluster_test clean

Clean up all test artifacts and processes.

mix cluster_test clean

mix cluster_test run

Run the full test cycle: start cluster → run tests → cleanup.

mix cluster_test run --size 3

mix cluster_test preflight

Run pre-flight environment checks.

mix cluster_test preflight

mix cluster_test logs

Show logs from test nodes.

# All nodes
mix cluster_test logs

# Specific node
mix cluster_test logs test_node1

API Reference

Starting and Stopping

# Start cluster with options
{:ok, nodes} = ClusterTest.start_cluster(node_count: 4)

# Stop cluster
:ok = ClusterTest.stop_cluster()

Status and Health

# 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     #=> [...]

Cleanup

# Clean all artifacts
:ok = ClusterTest.clean_all()

# Get logs
{:ok, logs} = ClusterTest.get_logs()
{:ok, logs} = ClusterTest.get_logs("test_node1")

Utilities

# 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"}

Testing with Real Nodes

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
end

Run 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 stop

Troubleshooting

WSL/Ubuntu Issues

ClusterTest includes fixes for common WSL Ubuntu 24.04 issues:

# Check environment
mix cluster_test preflight

# Common fixes
epmd -daemon
sudo systemctl restart systemd-resolved

Port Conflicts

# Check what's using ports
netstat -tulpn | grep 4200

# Clean up processes
mix cluster_test clean
pkill -f test_node

Node Connection Issues

# Check EPMD
epmd -names

# Test connectivity
ping localhost
ping 127.0.0.1

Memory Issues

# Monitor cluster resource usage
mix cluster_test health

Error Diagnostics

ClusterTest provides comprehensive error diagnosis:

# Get diagnostic information
diagnosis = ClusterTest.diagnose_startup_failure(reason)
IO.puts(diagnosis.problem)
Enum.each(diagnosis.solutions, &IO.puts("  • #{&1}"))

Examples

Basic Cluster Test

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

Custom Test Configuration

# config/test.exs
config :cluster_test, :distributed_testing,
  http_port_base: 4300,
  dist_port_base: 9300,
  default_cluster_size: 3,
  startup_timeout: 45_000

Integration with CI/CD

# .github/workflows/test.yml
- name: Run distributed tests
  run: |
    epmd -daemon
    mix cluster_test run --size 2

Architecture

ClusterTest 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

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Support

Related Projects

  • Arsenal - OTP operation framework
  • Sandbox - Isolated execution environment