@@ -8,8 +8,9 @@ import lerna.akka.entityreplication.raft.RaftProtocol.{ Command, ForwardedComman
88import lerna .akka .entityreplication .raft .model ._
99import lerna .akka .entityreplication .raft .protocol .RaftCommands ._
1010import lerna .akka .entityreplication .raft .routing .MemberIndex
11+ import org .scalatest .Inside
1112
12- class RaftActorCandidateSpec extends TestKit (ActorSystem ()) with RaftActorSpecBase {
13+ class RaftActorCandidateSpec extends TestKit (ActorSystem ()) with RaftActorSpecBase with Inside {
1314
1415 import RaftActor ._
1516
@@ -164,17 +165,40 @@ class RaftActorCandidateSpec extends TestKit(ActorSystem()) with RaftActorSpecBa
164165 }
165166
166167 " メンバーの過半数に Accept されると Leader になる" in {
168+ val selfMemberIndex = createUniqueMemberIndex()
167169 val follower1MemberIndex = createUniqueMemberIndex()
168170 val follower2MemberIndex = createUniqueMemberIndex()
169171 val candidate = createRaftActor(
172+ selfMemberIndex = selfMemberIndex,
170173 otherMemberIndexes = Set (follower1MemberIndex, follower2MemberIndex),
171174 )
172- val term = Term .initial()
173- setState(candidate, Candidate , createCandidateData(term))
175+ val currentTerm = Term (1 )
176+ setState(candidate, Candidate , createCandidateData(currentTerm))
177+ inside(getState(candidate)) { state =>
178+ state.stateData.acceptedMembers should be(Set .empty)
179+ }
174180
175- candidate ! RequestVoteAccepted (term, follower1MemberIndex)
176- candidate ! RequestVoteAccepted (term, follower2MemberIndex)
177- getState(candidate).stateName should be(Leader )
181+ // The candidate will vote for itself.
182+ candidate ! RequestVoteAccepted (currentTerm, selfMemberIndex)
183+ inside(getState(candidate)) { state =>
184+ state.stateName should be(Candidate )
185+ state.stateData.currentTerm should be(currentTerm)
186+ state.stateData.acceptedMembers should be(Set (selfMemberIndex))
187+ }
188+
189+ // Denied from follower1, this situation could happen by a split vote.
190+ candidate ! RequestVoteDenied (currentTerm)
191+ inside(getState(candidate)) { state =>
192+ state.stateName should be(Candidate )
193+ state.stateData.currentTerm should be(currentTerm)
194+ state.stateData.acceptedMembers should be(Set (selfMemberIndex))
195+ }
196+
197+ candidate ! RequestVoteAccepted (currentTerm, follower2MemberIndex)
198+ inside(getState(candidate)) { state =>
199+ state.stateName should be(Leader )
200+ state.stateData.currentTerm should be(currentTerm)
201+ }
178202 }
179203
180204 " become a Follower and agree to a Term if it receives RequestVoteDenied with newer Term" in {
@@ -192,6 +216,42 @@ class RaftActorCandidateSpec extends TestKit(ActorSystem()) with RaftActorSpecBa
192216 val state = getState(candidate)
193217 state.stateName should be(Follower )
194218 state.stateData.currentTerm should be(newerTerm)
219+ state.stateData.leaderMember should be(None )
220+ }
221+
222+ " not become the leader by duplicated votes from the same follower" in {
223+ val selfMemberIndex = createUniqueMemberIndex()
224+ val follower1MemberIndex = createUniqueMemberIndex()
225+ val follower2MemberIndex = createUniqueMemberIndex()
226+ val candidate = createRaftActor(
227+ selfMemberIndex = selfMemberIndex,
228+ otherMemberIndexes = Set (follower1MemberIndex, follower2MemberIndex),
229+ )
230+ val currentTerm = Term (1 )
231+ setState(candidate, Candidate , createCandidateData(currentTerm))
232+ inside(getState(candidate)) { state =>
233+ state.stateData.acceptedMembers should be(Set .empty)
234+ }
235+
236+ // For simplicity, this test skips that the candidate votes for itself.
237+
238+ // The first vote from follower1
239+ candidate ! RequestVoteAccepted (currentTerm, follower1MemberIndex)
240+ inside(getState(candidate)) { state =>
241+ state.stateName should be(Candidate )
242+ state.stateData.currentTerm should be(currentTerm)
243+ state.stateData.acceptedMembers should be(Set (follower1MemberIndex))
244+ }
245+
246+ // The second duplicated vote from follower1.
247+ // The candidate shouldn't become the leader
248+ // since it doesn't receive votes from the majority of the members.
249+ candidate ! RequestVoteAccepted (currentTerm, follower1MemberIndex)
250+ inside(getState(candidate)) { state =>
251+ state.stateName should be(Candidate )
252+ state.stateData.currentTerm should be(currentTerm)
253+ state.stateData.acceptedMembers should be(Set (follower1MemberIndex))
254+ }
195255 }
196256
197257 " AppendEntries が古い Term を持っているときは拒否" in {
0 commit comments