feat(batch30): implement raft group-c run loop behaviors
This commit is contained in:
@@ -108,4 +108,98 @@ public sealed class RaftNodeCoreTests
|
||||
raft.Peers_.Count.ShouldBe(2);
|
||||
raft.RandCampaignTimeout().ShouldBeGreaterThan(TimeSpan.Zero);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunLoopHelpers_WhenInvoked_ShouldManageSubscriptionAndTimers()
|
||||
{
|
||||
var raft = new Raft
|
||||
{
|
||||
Id = "N1",
|
||||
GroupName = "RG",
|
||||
Active = DateTime.UtcNow,
|
||||
};
|
||||
|
||||
var inbox = raft.NewInbox();
|
||||
var catchupInbox = raft.NewCatchupInbox();
|
||||
inbox.ShouldContain("_INBOX.");
|
||||
catchupInbox.ShouldContain("_INBOX.CATCHUP.");
|
||||
|
||||
var sub = raft.Subscribe("raft.append");
|
||||
raft.AeSub.ShouldNotBeNull();
|
||||
raft.Unsubscribe(sub);
|
||||
raft.AeSub.ShouldBeNull();
|
||||
raft.CreateInternalSubs().ShouldBeNull();
|
||||
|
||||
raft.ResetElectionTimeout();
|
||||
raft.Elect.ShouldNotBeNull();
|
||||
raft.ResetElect(TimeSpan.FromMilliseconds(10));
|
||||
raft.ElectTimer().ShouldBeGreaterThan(DateTime.MinValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CodecHelpers_WhenRoundTrippingEntriesAndVotes_ShouldPreserveFields()
|
||||
{
|
||||
var raft = new Raft
|
||||
{
|
||||
GroupName = "RG",
|
||||
};
|
||||
|
||||
var entry = raft.NewEntry(EntryType.EntryNormal, [1, 2, 3]);
|
||||
var proposed = raft.NewProposedEntry(entry, "reply");
|
||||
proposed.Reply.ShouldBe("reply");
|
||||
|
||||
var appendEntry = raft.NewAppendEntry("L1", 2, 1, 1, 0, [entry]);
|
||||
appendEntry.String().ShouldContain("leader=L1");
|
||||
appendEntry.ShouldStore().ShouldBeTrue();
|
||||
var decodedAppend = raft.DecodeAppendEntry(appendEntry.Encode());
|
||||
decodedAppend.Leader.ShouldBe("L1");
|
||||
|
||||
var appendResponse = raft.NewAppendEntryResponse(2, 1, "N2", "_R_", success: true);
|
||||
var decodedResponse = raft.DecodeAppendEntryResponse(appendResponse.Encode());
|
||||
decodedResponse.Success.ShouldBeTrue();
|
||||
decodedResponse.Peer.ShouldBe("N2");
|
||||
|
||||
var voteRequest = new VoteRequest { TermV = 4, Candidate = "N3", LastIndex = 9, LastTerm = 3, Reply = "_R_" };
|
||||
var decodedVoteRequest = raft.DecodeVoteRequest(voteRequest.Encode());
|
||||
decodedVoteRequest.Candidate.ShouldBe("N3");
|
||||
|
||||
var voteResponse = new VoteResponse { TermV = 4, Peer = "N2", Granted = true, Empty = false };
|
||||
var decodedVoteResponse = raft.DecodeVoteResponse(voteResponse.Encode());
|
||||
decodedVoteResponse.Granted.ShouldBeTrue();
|
||||
decodedVoteResponse.Peer.ShouldBe("N2");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PeerStatePersistence_WhenWrittenAndRead_ShouldRoundTrip()
|
||||
{
|
||||
var raft = new Raft();
|
||||
var storeDir = Path.Combine(Path.GetTempPath(), $"raft-peer-state-{Guid.NewGuid():N}");
|
||||
try
|
||||
{
|
||||
var state = new PeerState
|
||||
{
|
||||
KnownPeers = ["A1", "A2"],
|
||||
ClusterSize = 2,
|
||||
DomainExt = 7,
|
||||
};
|
||||
|
||||
raft.PeerStateBufSize(state).ShouldBeGreaterThan(0);
|
||||
raft.WritePeerStateStatic(storeDir, state).ShouldBeNull();
|
||||
var (readState, readError) = raft.ReadPeerState(storeDir);
|
||||
readError.ShouldBeNull();
|
||||
readState.ShouldNotBeNull();
|
||||
readState!.KnownPeers.Count.ShouldBe(2);
|
||||
|
||||
raft.WriteTermVoteStatic(storeDir, 6, "A1").ShouldBeNull();
|
||||
raft.Term_.ShouldBe(6UL);
|
||||
raft.Vote.ShouldBe("A1");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(storeDir))
|
||||
{
|
||||
Directory.Delete(storeDir, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,4 +138,35 @@ public sealed class RaftTypesTests
|
||||
checkpoint.Abort();
|
||||
File.Exists(checkpoint.SnapFile).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EntryAndPoolHelpers_ShouldReturnExpectedRepresentations()
|
||||
{
|
||||
EntryType.EntryAddPeer.String().ShouldBe("EntryAddPeer");
|
||||
|
||||
var committed = new CommittedEntry { Index = 2, Entries = [new Entry { Type = EntryType.EntryNormal, Data = [1] }] };
|
||||
committed.ReturnToPool();
|
||||
committed.Index.ShouldBe(0UL);
|
||||
committed.Entries.ShouldBeEmpty();
|
||||
|
||||
var proposed = new ProposedEntry { Entry = new Entry { Type = EntryType.EntryNormal, Data = [2] }, Reply = "_R_" };
|
||||
proposed.ReturnToPool();
|
||||
proposed.Entry.ShouldBeNull();
|
||||
proposed.Reply.ShouldBeEmpty();
|
||||
|
||||
var appendEntry = new AppendEntry
|
||||
{
|
||||
Leader = "N1",
|
||||
TermV = 2,
|
||||
Commit = 1,
|
||||
PTerm = 1,
|
||||
PIndex = 0,
|
||||
Entries = [new Entry { Type = EntryType.EntryNormal, Data = [3] }],
|
||||
Reply = "_R_",
|
||||
};
|
||||
|
||||
appendEntry.ReturnToPool();
|
||||
appendEntry.Leader.ShouldBeEmpty();
|
||||
appendEntry.Entries.ShouldBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user