File: leader-election/testleaderelection.py

Date: 2026-05-29

Time: 13:19

Purpose

This is the test suite for a Bully Algorithm leader election implementation. It validates that the BullyElectionCluster correctly implements the core properties of the Bully Algorithm: the highest-ID alive node always becomes leader, leadership transfers on failure, and a recovered high-ID node "bullies" its way back to leadership.

The file serves as both a pytest-compatible test module and a standalone runner (via _main_), making it usable in CI and ad-hoc debugging alike.

Key Components

Test Functions

| Test | What it verifies |

|------|-----------------|

| testinitialelectionhighestwins | Node 5 wins initial election in a 5-node cluster |

| testallnodesagreeon_leader | All nodes converge — followers point to the same leader, leader reports "leader" state |

| testleaderfailuretriggersnew_election | Failing node 5 causes node 4 to take over |

| testrecoverednodetakesover | Recovering node 5 reclaims leadership (the "bully" property) |

| testmultiplefailures | Failing nodes 5 and 4 leaves node 3 as leader |

| testsinglesurvivor | Only node 1 alive → node 1 is leader |

| testsinglenode_cluster | Degenerate cluster of size 1 handles election correctly |

| testelectionhistory | getelectionhistory() records at least 3 leadership transitions across fail/recover cycles |

| testtermsincrease_monotonically | Election terms never decrease across successive history entries |

| testexamplefrom_spec | End-to-end scenario exercising initial election → failure → recovery → multi-failure |

Standalone Runner (lines 107–124)

A manual test harness that runs all tests sequentially, prints PASS/FAIL per test, and exits with code 1 on any failure. This mirrors what pytest does but allows running without pytest installed.

Patterns

Deterministic simulation. The cluster is not running real timers or threads. Instead, rununtilleader(start_time=N) advances a simulated clock, making tests fully deterministic and free of flaky timing issues. Each test controls the timeline explicitly.

Fail-then-assert. Tests follow a consistent structure: set up a cluster → trigger a failure/recovery → run election → assert the expected leader. This isolates each Bully Algorithm property into a single test.

State-based verification. Rather than inspecting messages, most tests check the resulting state (getclusterstate(), getelectionhistory()) — treating the cluster as a black box and verifying its externally visible properties.

Cumulative scenario. testexamplefrom_spec is a full integration test that chains multiple operations (elect → fail → re-elect → recover → re-elect → multi-fail → re-elect) in sequence, verifying the complete lifecycle.

Dependencies

Imports:

Imported by: Nothing — this is a leaf test file.

Flow

Each test follows this pattern:

1. Construct a BullyElectionCluster with a list of node IDs and timing parameters (heartbeatinterval=3, electiontimeout=10).

2. Run cluster.rununtilleader() to simulate the election process until a leader emerges.

3. Optionally mutate the cluster via failnode(id) or recovernode(id).

4. Re-run rununtilleader(start_time=N) with a later simulated time to trigger re-election.

5. Assert the returned leader ID matches the expected highest-alive node.

6. Optionally inspect cluster-wide state or history for deeper invariant checks.

The start_time parameter is critical — it provides temporal separation between election rounds so that heartbeat timeouts from the previous leader have expired and surviving nodes detect the failure.

Invariants

1. Bully invariant: The leader is always the highest-ID alive node. Every test implicitly validates this.

2. Consensus: After rununtilleader(), all alive nodes agree on the leader (testallnodesagreeon_leader).

3. Monotonic terms: Election terms never decrease across the history (testtermsincrease_monotonically).

4. History completeness: At least 3 history entries are recorded across 3 election rounds (testelectionhistory).

5. Leader/follower role separation: The leader node reports state "leader", all others report "follower".

Error Handling

Tests use bare assert with descriptive f-string messages — no try/except within individual tests. The standalone runner catches all exceptions per-test to continue running the full suite and report aggregate results. There's no retry logic or tolerance for flakiness, which is appropriate given the deterministic simulation model.

Topics to Explore

Beliefs