+
+ @Test
+ public void testHandleAppendEntriesAddSameEntryToLog(){
+ new JavaTestKit(getSystem()) {
+ {
+
+ MockRaftActorContext context = createActorContext();
+
+ // First set the receivers term to lower number
+ context.getTermInformation().update(2, "test");
+
+ // Prepare the receivers log
+ MockRaftActorContext.SimpleReplicatedLog log =
+ new MockRaftActorContext.SimpleReplicatedLog();
+ log.append(
+ new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
+
+ context.setReplicatedLog(log);
+
+ List<ReplicatedLogEntry> entries = new ArrayList<>();
+ entries.add(
+ new MockRaftActorContext.MockReplicatedLogEntry(1, 0, new MockRaftActorContext.MockPayload("zero")));
+
+ AppendEntries appendEntries =
+ new AppendEntries(2, "leader-1", -1, 1, entries, 0, -1);
+
+ RaftActorBehavior behavior = createBehavior(context);
+
+ if (AbstractRaftActorBehaviorTest.this instanceof CandidateTest) {
+ // Resetting the Candidates term to make sure it will match
+ // the term sent by AppendEntries. If this was not done then
+ // the test will fail because the Candidate will assume that
+ // the message was sent to it from a lower term peer and will
+ // thus respond with a failure
+ context.getTermInformation().update(2, "test");
+ }
+
+ // Send an unknown message so that the state of the RaftActor remains unchanged
+ RaftActorBehavior expected = behavior.handleMessage(getRef(), "unknown");
+
+ RaftActorBehavior raftBehavior =
+ behavior.handleMessage(getRef(), appendEntries);
+
+ assertEquals(expected, raftBehavior);
+
+ assertEquals(1, log.size());
+
+
+ }};
+ }
+
+ /**
+ * This test verifies that when a RequestVote is received by the RaftActor
+ * with a term which is greater than the RaftActors' currentTerm and the
+ * senders' log is more upto date than the receiver that the receiver grants
+ * the vote to the sender
+ */
+ @Test
+ public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermAndSenderLogMoreUpToDate() {
+ new JavaTestKit(getSystem()) {{
+
+ new Within(duration("1 seconds")) {
+ @Override
+ protected void run() {
+
+ RaftActorBehavior behavior = createBehavior(
+ createActorContext(behaviorActor));
+
+ RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(),
+ new RequestVote(1000, "test", 10000, 999));
+
+ if(!(behavior instanceof Follower)){
+ assertTrue(raftBehavior instanceof Follower);
+ } else {
+
+ final Boolean out =
+ new ExpectMsg<Boolean>(duration("1 seconds"),
+ "RequestVoteReply") {
+ // do not put code outside this method, will run afterwards
+ @Override
+ protected Boolean match(Object in) {
+ if (in instanceof RequestVoteReply) {
+ RequestVoteReply reply =
+ (RequestVoteReply) in;
+ return reply.isVoteGranted();
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get();
+
+ assertEquals(true, out);
+ }
+ }
+ };
+ }};
+ }
+
+ /**
+ * This test verifies that when a RaftActor receives a RequestVote message
+ * with a term that is greater than it's currentTerm but a less up-to-date
+ * log then the receiving RaftActor will not grant the vote to the sender
+ */
+ @Test
+ public void testHandleRequestVoteWhenSenderTermGreaterThanCurrentTermButSenderLogLessUptoDate() {
+ new JavaTestKit(getSystem()) {{
+
+ new Within(duration("1 seconds")) {
+ @Override
+ protected void run() {
+
+ RaftActorContext actorContext =
+ createActorContext(behaviorActor);
+
+ MockRaftActorContext.SimpleReplicatedLog
+ log = new MockRaftActorContext.SimpleReplicatedLog();
+ log.append(
+ new MockRaftActorContext.MockReplicatedLogEntry(20000,
+ 1000000, new MockRaftActorContext.MockPayload("")));
+
+ ((MockRaftActorContext) actorContext).setReplicatedLog(log);
+
+ RaftActorBehavior behavior = createBehavior(actorContext);
+
+ RaftActorBehavior raftBehavior = behavior.handleMessage(getTestActor(),
+ new RequestVote(1000, "test", 10000, 999));
+
+ if(!(behavior instanceof Follower)){
+ assertTrue(raftBehavior instanceof Follower);
+ } else {
+ final Boolean out =
+ new ExpectMsg<Boolean>(duration("1 seconds"),
+ "RequestVoteReply") {
+ // do not put code outside this method, will run afterwards
+ @Override
+ protected Boolean match(Object in) {
+ if (in instanceof RequestVoteReply) {
+ RequestVoteReply reply =
+ (RequestVoteReply) in;
+ return reply.isVoteGranted();
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get();
+
+ assertEquals(false, out);
+ }
+ }
+ };
+ }};
+ }
+
+
+
+ /**
+ * This test verifies that the receiving RaftActor will not grant a vote
+ * to a sender if the sender's term is lesser than the currentTerm of the
+ * recipient RaftActor
+ */
+ @Test
+ public void testHandleRequestVoteWhenSenderTermLessThanCurrentTerm() {
+ new JavaTestKit(getSystem()) {{
+
+ new Within(duration("1 seconds")) {
+ @Override
+ protected void run() {
+
+ RaftActorContext context =
+ createActorContext(behaviorActor);
+
+ context.getTermInformation().update(1000, null);
+
+ RaftActorBehavior follower = createBehavior(context);
+
+ follower.handleMessage(getTestActor(),
+ new RequestVote(999, "test", 10000, 999));
+
+ final Boolean out =
+ new ExpectMsg<Boolean>(duration("1 seconds"),
+ "RequestVoteReply") {
+ // do not put code outside this method, will run afterwards
+ @Override
+ protected Boolean match(Object in) {
+ if (in instanceof RequestVoteReply) {
+ RequestVoteReply reply =
+ (RequestVoteReply) in;
+ return reply.isVoteGranted();
+ } else {
+ throw noMatch();
+ }
+ }
+ }.get();
+
+ assertEquals(false, out);
+ }
+ };
+ }};
+ }
+
+ @Test
+ public void testPerformSnapshot() {
+ MockRaftActorContext context = new MockRaftActorContext("test", getSystem(), behaviorActor);
+ AbstractRaftActorBehavior abstractBehavior = (AbstractRaftActorBehavior) createBehavior(context);
+ if (abstractBehavior instanceof Candidate) {
+ return;
+ }
+
+ context.getTermInformation().update(1, "test");
+
+ //log has 1 entry with replicatedToAllIndex = 0, does not do anything, returns the
+ context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 1, 1).build());
+ context.setLastApplied(0);
+ abstractBehavior.performSnapshotWithoutCapture(0);
+ assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
+ assertEquals(1, context.getReplicatedLog().size());
+
+ //2 entries, lastApplied still 0, no purging.
+ context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
+ context.setLastApplied(0);
+ abstractBehavior.performSnapshotWithoutCapture(0);
+ assertEquals(-1, abstractBehavior.getReplicatedToAllIndex());
+ assertEquals(2, context.getReplicatedLog().size());
+
+ //2 entries, lastApplied still 0, no purging.
+ context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 2, 1).build());
+ context.setLastApplied(1);
+ abstractBehavior.performSnapshotWithoutCapture(0);
+ assertEquals(0, abstractBehavior.getReplicatedToAllIndex());
+ assertEquals(1, context.getReplicatedLog().size());
+
+ //5 entries, lastApplied =2 and replicatedIndex = 3, but since we want to keep the lastapplied, indices 0 and 1 will only get purged
+ context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 5, 1).build());
+ context.setLastApplied(2);
+ abstractBehavior.performSnapshotWithoutCapture(3);
+ assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
+ assertEquals(3, context.getReplicatedLog().size());
+
+ // scenario where Last applied > Replicated to all index (becoz of a slow follower)
+ context.setReplicatedLog(new MockRaftActorContext.MockReplicatedLogBuilder().createEntries(0, 3, 1).build());
+ context.setLastApplied(2);
+ abstractBehavior.performSnapshotWithoutCapture(1);
+ assertEquals(1, abstractBehavior.getReplicatedToAllIndex());
+ assertEquals(1, context.getReplicatedLog().size());
+ }
+
+