From 165ac654e8458d7f91b0158863eccb8d1f1f6031 Mon Sep 17 00:00:00 2001 From: Xiang Li Date: Sat, 1 Nov 2014 21:15:28 -0700 Subject: [PATCH] raft: add paper tests for section 5.4.1 --- raft/raft_paper_test.go | 126 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 125 insertions(+), 1 deletion(-) diff --git a/raft/raft_paper_test.go b/raft/raft_paper_test.go index 93201647a..546a0ebc1 100644 --- a/raft/raft_paper_test.go +++ b/raft/raft_paper_test.go @@ -233,7 +233,6 @@ func TestLeaderElectionInOneRoundRPC(t *testing.T) { // TestFollowerVote tests that each follower will vote for at most one // candidate in a given term, on a first-come-first-served basis. -// TODO: (note: Section 5.4 adds an additional restriction on votes). // Reference: section 5.2 func TestFollowerVote(t *testing.T) { tests := []struct { @@ -746,6 +745,131 @@ func TestLeaderSyncFollowerLog(t *testing.T) { } } +// TestVoteRequest tests that the vote request includes information about the candidate’s log +// and are sent to all of the other nodes. +// Reference: section 5.4.1 +func TestVoteRequest(t *testing.T) { + tests := []struct { + ents []pb.Entry + wterm uint64 + }{ + {[]pb.Entry{{Term: 1, Index: 1}}, 2}, + {[]pb.Entry{{Term: 1, Index: 1}, {Term: 2, Index: 2}}, 3}, + } + for i, tt := range tests { + r := newRaft(1, []uint64{1, 2, 3}, 10, 1) + r.Step(pb.Message{ + From: 2, To: 1, Type: pb.MsgApp, Term: tt.wterm - 1, LogTerm: 0, Index: 0, Entries: tt.ents, + }) + r.readMessages() + + for i := 0; i < r.electionTimeout*2; i++ { + r.tickElection() + } + + msgs := r.readMessages() + sort.Sort(messageSlice(msgs)) + if len(msgs) != 2 { + t.Fatalf("#%d: len(msg) = %d, want %d", i, len(msgs), 2) + } + for i, m := range msgs { + if m.Type != pb.MsgVote { + t.Errorf("#%d: msgType = %d, want %d", i, m.Type, pb.MsgVote) + } + if m.To != uint64(i+2) { + t.Errorf("#%d: to = %d, want %d", i, m.To, i+2) + } + if m.Term != tt.wterm { + t.Errorf("#%d: term = %d, want %d", i, m.Term, tt.wterm) + } + windex, wlogterm := tt.ents[len(tt.ents)-1].Index, tt.ents[len(tt.ents)-1].Term + if m.Index != windex { + t.Errorf("#%d: index = %d, want %d", i, m.Index, windex) + } + if m.LogTerm != wlogterm { + t.Errorf("#%d: logterm = %d, want %d", i, m.LogTerm, wlogterm) + } + } + } +} + +// TestVoter tests the voter denies its vote if its own log is more up-to-date +// than that of the candidate. +// Reference: section 5.4.1 +func TestVoter(t *testing.T) { + tests := []struct { + ents []pb.Entry + logterm uint64 + index uint64 + + wreject bool + }{ + // same logterm + {[]pb.Entry{{}, {Term: 1, Index: 1}}, 1, 1, false}, + {[]pb.Entry{{}, {Term: 1, Index: 1}}, 1, 2, false}, + {[]pb.Entry{{}, {Term: 1, Index: 1}, {Term: 1, Index: 2}}, 1, 1, true}, + // candidate higher logterm + {[]pb.Entry{{}, {Term: 1, Index: 1}}, 2, 1, false}, + {[]pb.Entry{{}, {Term: 1, Index: 1}}, 2, 2, false}, + {[]pb.Entry{{}, {Term: 1, Index: 1}, {Term: 1, Index: 2}}, 2, 1, false}, + // voter higher logterm + {[]pb.Entry{{}, {Term: 2, Index: 1}}, 1, 1, true}, + {[]pb.Entry{{}, {Term: 2, Index: 1}}, 1, 2, true}, + {[]pb.Entry{{}, {Term: 2, Index: 1}, {Term: 1, Index: 2}}, 1, 1, true}, + } + for i, tt := range tests { + r := newRaft(1, []uint64{1, 2}, 10, 1) + r.loadEnts(tt.ents) + + r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgVote, Term: 3, LogTerm: tt.logterm, Index: tt.index}) + + msgs := r.readMessages() + if len(msgs) != 1 { + t.Fatalf("#%d: len(msg) = %d, want %d", i, len(msgs), 1) + } + m := msgs[0] + if m.Type != pb.MsgVoteResp { + t.Errorf("#%d: msgType = %d, want %d", i, m.Type, pb.MsgVoteResp) + } + if m.Reject != tt.wreject { + t.Errorf("#%d: reject = %t, want %t", i, m.Reject, tt.wreject) + } + } +} + +// TestLeaderOnlyCommitsLogFromCurrentTerm tests that only log entries from the leader’s +// current term are committed by counting replicas. +// Reference: section 5.4.2 +func TestLeaderOnlyCommitsLogFromCurrentTerm(t *testing.T) { + ents := []pb.Entry{{}, {Term: 1, Index: 1}, {Term: 2, Index: 2}} + tests := []struct { + index uint64 + wcommit uint64 + }{ + // do not commit log entries in previous terms + {1, 0}, + {2, 0}, + // commit log in current term + {3, 3}, + } + for i, tt := range tests { + r := newRaft(1, []uint64{1, 2}, 10, 1) + r.loadEnts(ents) + r.loadState(pb.HardState{Term: 2}) + // become leader at term 3 + r.becomeCandidate() + r.becomeLeader() + r.readMessages() + // propose a entry to current term + r.Step(pb.Message{From: 1, To: 1, Type: pb.MsgProp, Entries: []pb.Entry{{}}}) + + r.Step(pb.Message{From: 2, To: 1, Type: pb.MsgAppResp, Term: r.Term, Index: tt.index}) + if r.raftLog.committed != tt.wcommit { + t.Errorf("#%d: commit = %d, want %d", i, r.raftLog.committed, tt.wcommit) + } + } +} + type messageSlice []pb.Message func (s messageSlice) Len() int { return len(s) }