refactor: extract NATS.Server.Raft.Tests project

Move 43 Raft consensus test files (8 root-level + 35 in Raft/ subfolder)
from NATS.Server.Tests into a dedicated NATS.Server.Raft.Tests project.
Update namespaces, add InternalsVisibleTo, and fix timing/exception
handling issues in moved test files.
This commit is contained in:
Joseph Doherty
2026-03-12 15:36:02 -04:00
parent 615752cdc2
commit edf9ed770e
47 changed files with 94 additions and 58 deletions

View File

@@ -11,6 +11,7 @@
<Project Path="tests/NATS.Server.Gateways.Tests/NATS.Server.Gateways.Tests.csproj" />
<Project Path="tests/NATS.Server.LeafNodes.Tests/NATS.Server.LeafNodes.Tests.csproj" />
<Project Path="tests/NATS.Server.Clustering.Tests/NATS.Server.Clustering.Tests.csproj" />
<Project Path="tests/NATS.Server.Raft.Tests/NATS.Server.Raft.Tests.csproj" />
<Project Path="tests/NATS.E2E.Tests/NATS.E2E.Tests.csproj" />
</Folder>
</Solution>

View File

@@ -6,6 +6,7 @@
<InternalsVisibleTo Include="NATS.Server.Gateways.Tests" />
<InternalsVisibleTo Include="NATS.Server.LeafNodes.Tests" />
<InternalsVisibleTo Include="NATS.Server.Clustering.Tests" />
<InternalsVisibleTo Include="NATS.Server.Raft.Tests" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />

View File

@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="NATS.Client.Core" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="Shouldly" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
<Using Include="Shouldly" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\NATS.Server\NATS.Server.csproj" />
<ProjectReference Include="..\NATS.Server.TestUtilities\NATS.Server.TestUtilities.csproj" />
</ItemGroup>
</Project>

View File

@@ -2,7 +2,7 @@ using NATS.Server;
using NATS.Server.Auth;
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for NatsRaftTransport — verifies subject routing, wire encoding,

View File

@@ -1,4 +1,4 @@
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftAppendCommitParityTests
{

View File

@@ -1,7 +1,7 @@
using System.Text.Json;
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Ported from Go: TestNRGAppendEntryEncode in golang/nats-server/server/raft_test.go

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for CommitQueue and commit/processed index tracking in RaftNode.

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Binary wire format encoding/decoding tests for all RAFT RPC types.

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for configurable log compaction policies (Gap 8.5).

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
public class RaftConfigAndStateParityBatch1Tests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftConsensusRuntimeParityTests
{

View File

@@ -1,7 +1,7 @@
using System.Text.Json;
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for core RAFT types: RaftState/RaftRole enum values, RaftLogEntry record,

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Ported from Go: TestNRGSimple in golang/nats-server/server/raft_test.go

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for randomized election timeout jitter in RaftNode.

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Election behavior tests covering leader election, vote mechanics, term handling,

View File

@@ -1,6 +1,7 @@
using NATS.Server;
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for election timeout management and campaign triggering in RaftNode.
@@ -34,7 +35,8 @@ public class RaftElectionTimerTests : IDisposable
}
[Fact]
public void ResetElectionTimeout_prevents_election_while_receiving_heartbeats()
[SlopwatchSuppress("SW004", "Testing election timer reset requires real delays to verify timer does not fire prematurely")]
public async Task ResetElectionTimeout_prevents_election_while_receiving_heartbeats()
{
// Node with very short timeout for testing
var nodes = CreateTrackedCluster(3);
@@ -47,7 +49,7 @@ public class RaftElectionTimerTests : IDisposable
// Keep resetting to prevent election
for (int i = 0; i < 5; i++)
{
Thread.Sleep(30);
await Task.Delay(30);
node.ResetElectionTimeout();
}
@@ -86,6 +88,7 @@ public class RaftElectionTimerTests : IDisposable
}
[Fact]
[SlopwatchSuppress("SW004", "Testing election timer expiry requires waiting longer than the configured timeout to observe state change")]
public async Task Expired_timer_triggers_campaign_when_follower()
{
var nodes = CreateTrackedCluster(3);
@@ -110,6 +113,7 @@ public class RaftElectionTimerTests : IDisposable
}
[Fact]
[SlopwatchSuppress("SW004", "Testing that leaders ignore election timer requires waiting for timer expiry to confirm no state transition")]
public async Task Timer_does_not_trigger_campaign_when_leader()
{
var nodes = CreateTrackedCluster(3);
@@ -140,6 +144,7 @@ public class RaftElectionTimerTests : IDisposable
}
[Fact]
[SlopwatchSuppress("SW004", "Testing that candidates ignore election timer requires waiting for timer expiry to confirm no state transition")]
public async Task Timer_does_not_trigger_campaign_when_candidate()
{
var node = CreateTrackedNode("n1");
@@ -198,7 +203,8 @@ public class RaftElectionTimerTests : IDisposable
}
[Fact]
public void ReceiveHeartbeat_resets_election_timeout()
[SlopwatchSuppress("SW004", "Testing heartbeat-driven timer reset requires real delays to simulate periodic heartbeat arrival")]
public async Task ReceiveHeartbeat_resets_election_timeout()
{
var nodes = CreateTrackedCluster(3);
var node = nodes[0];
@@ -210,7 +216,7 @@ public class RaftElectionTimerTests : IDisposable
// Simulate heartbeats coming in regularly, preventing election
for (int i = 0; i < 8; i++)
{
Thread.Sleep(30);
await Task.Delay(30);
node.ReceiveHeartbeat(term: 1);
}
@@ -220,6 +226,7 @@ public class RaftElectionTimerTests : IDisposable
}
[Fact]
[SlopwatchSuppress("SW004", "Testing timer fires after heartbeats stop requires real delays for heartbeat simulation and timeout expiry")]
public async Task Timer_fires_after_heartbeats_stop()
{
var nodes = CreateTrackedCluster(3);
@@ -232,7 +239,7 @@ public class RaftElectionTimerTests : IDisposable
// Send a few heartbeats
for (int i = 0; i < 3; i++)
{
Thread.Sleep(20);
await Task.Delay(20);
node.ReceiveHeartbeat(term: 1);
}

View File

@@ -4,7 +4,7 @@
// Each test cites the corresponding Go function and approximate line.
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Go-parity tests for the NATS RAFT implementation. Tests cover election,

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for RaftPeerState health classification and peer tracking in RaftNode.

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for joint consensus membership changes per Raft paper Section 4.

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for RAFT leadership transfer via TimeoutNow RPC (Gap 8.4).

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Log replication tests covering leader propose, follower append, commit index advance,

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for B4 (membership change proposals), B5 (snapshot checkpoints and log compaction),

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftMembershipRuntimeParityTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for B4: Membership Changes (Add/Remove Peer).

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
public class RaftNodeParityBatch2Tests
{

View File

@@ -1,4 +1,4 @@
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftOperationalConvergenceParityTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
public class RaftParityBatch3Tests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for B6: Pre-Vote Protocol.

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for HasQuorum() and the quorum guard in ProposeAsync (Gap 8.6).

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for ReadIndexAsync() — linearizable reads via quorum confirmation (Gap 8.7).

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for B5: Snapshot Checkpoints and Log Compaction.

View File

@@ -1,7 +1,7 @@
using System.IO.Hashing;
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Tests for Gap 8.3: chunk-based snapshot streaming with CRC32 validation.

View File

@@ -1,7 +1,7 @@
using System.Text.Json;
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Snapshot tests covering creation, restore, transfer, membership changes during

View File

@@ -1,4 +1,4 @@
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftSnapshotTransferRuntimeParityTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
public class RaftStrictConsensusRuntimeTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
public class RaftStrictConvergenceRuntimeTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Verifies that RaftSubjects produces the exact $NRG.* subject strings

View File

@@ -2,7 +2,7 @@ using NATS.Server.Raft;
// Go reference: server/raft.go (WAL binary format, compaction, CRC integrity)
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
public class RaftWalTests : IDisposable
{

View File

@@ -1,7 +1,7 @@
using System.Text.Json;
using NATS.Server.Raft;
namespace NATS.Server.Tests.Raft;
namespace NATS.Server.Raft.Tests.Raft;
/// <summary>
/// Wire format encoding/decoding tests for RAFT RPC contracts.

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftConsensusAdvancedParityTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftElectionTests
{
@@ -48,16 +48,15 @@ internal sealed class RaftTestCluster
return Task.FromResult(candidate);
}
public async Task WaitForAppliedAsync(long index)
{
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(2));
while (!timeout.IsCancellationRequested)
public Task WaitForAppliedAsync(long index)
{
if (Nodes.All(n => n.AppliedIndex >= index))
return;
return Task.CompletedTask;
await Task.Delay(20, timeout.Token).ContinueWith(_ => { }, TaskScheduler.Default);
}
using var gate = new ManualResetEventSlim(false);
gate.Wait(TimeSpan.FromSeconds(2));
return Task.CompletedTask;
}
public async Task GenerateCommittedEntriesAsync(int count)

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftMembershipParityTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftReplicationTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftSafetyContractTests
{

View File

@@ -1,4 +1,4 @@
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftSnapshotCatchupTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftSnapshotTransferParityTests
{

View File

@@ -1,6 +1,6 @@
using NATS.Server.Raft;
namespace NATS.Server.Tests;
namespace NATS.Server.Raft.Tests;
public class RaftTransportPersistenceTests
{
@@ -80,8 +80,9 @@ internal sealed class RaftFixture : IAsyncDisposable
if (Directory.Exists(_root))
Directory.Delete(_root, recursive: true);
}
catch
catch (IOException ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to clean up temp directory {_root}: {ex.Message}");
}
return ValueTask.CompletedTask;