diff options
| author | Paul Buetow <paul@buetow.org> | 2023-05-20 01:20:30 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2023-05-20 01:20:30 +0300 |
| commit | 62f88b8d92b3358213f9af765422f871494ed93b (patch) | |
| tree | 422546b11149185ce4dca8b45bb83ff6cfe5dd64 | |
| parent | 5b62dff074a0ef52c9ba1bb87ced6947d9bf6782 (diff) | |
can break tie score
| -rw-r--r-- | internal/config.go | 11 | ||||
| -rw-r--r-- | internal/config_test.go | 25 | ||||
| -rw-r--r-- | internal/quorum.go | 11 | ||||
| -rw-r--r-- | internal/quorum_test.go | 82 |
4 files changed, 125 insertions, 4 deletions
diff --git a/internal/config.go b/internal/config.go index 17ec25b..21df3b9 100644 --- a/internal/config.go +++ b/internal/config.go @@ -2,6 +2,7 @@ package internal import ( "encoding/json" + "fmt" "io/ioutil" "log" "net" @@ -37,6 +38,16 @@ func newConfig(configFile string) (config, error) { return config, nil } +func (c config) participantNumber(participant string) (int, error) { + for i, participant_ := range c.Participants { + if participant == stripPort(participant_) { + return i, nil + } + } + + return 0, fmt.Errorf("participant %s not found", participant) +} + func (c config) isParticipant(remoteAddr string) bool { remoteAddr = stripPort(remoteAddr) diff --git a/internal/config_test.go b/internal/config_test.go index 67a9fff..d1974b5 100644 --- a/internal/config_test.go +++ b/internal/config_test.go @@ -12,6 +12,31 @@ func TestStripPort(t *testing.T) { } } +func TestParticipantNumber(t *testing.T) { + config := config{Participants: []string{"localhost:1234", "hamburger:4321"}} + + num, err := config.participantNumber("localhost") + if err != nil { + t.Errorf(err.Error()) + } + if num != 0 { + t.Errorf("localhost should be participant number 0 but is %d", num) + } + + num, err = config.participantNumber("hamburger") + if err != nil { + t.Errorf(err.Error()) + } + if num != 1 { + t.Errorf("hamburger should be participant number 1 but is %d", num) + } + + _, err = config.participantNumber("doener") + if err == nil { + t.Errorf("doener is not a participant") + } +} + func TestIsParticipant(t *testing.T) { config := config{Participants: []string{"localhost:1234", "hamburger:4321"}} diff --git a/internal/quorum.go b/internal/quorum.go index 964d465..032ebc8 100644 --- a/internal/quorum.go +++ b/internal/quorum.go @@ -17,7 +17,7 @@ func (q quorumMap) vote(v vote) { q[v.from] = v } -func (q quorumMap) score() (scores []score) { +func (q quorumMap) score(config config) (scores []score) { q.cleanExpired() scoreMap := make(map[string]int) @@ -33,7 +33,14 @@ func (q quorumMap) score() (scores []score) { } sort.Slice(scores, func(i, j int) bool { - return scores[i].score < scores[j].score + if scores[i].score != scores[j].score { + return scores[i].score > scores[j].score + } + + // Score tie, use participant number. + i_, _ := config.participantNumber(scores[i].id) + j_, _ := config.participantNumber(scores[j].id) + return i_ < j_ }) return diff --git a/internal/quorum_test.go b/internal/quorum_test.go index 80c4598..da2abdd 100644 --- a/internal/quorum_test.go +++ b/internal/quorum_test.go @@ -5,15 +5,93 @@ import ( "time" ) +func TestScore(t *testing.T) { + quorum := make(quorumMap) + config := config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} + + vote1 := newVote(config, "foo:334234", "foo bar\n") + vote1.expiresAt = time.Now().Add(1 * time.Hour) + quorum.vote(vote1) + + vote2 := newVote(config, "bar:334234", "bar baz\n") + vote2.expiresAt = time.Now().Add(1 * time.Hour) + quorum.vote(vote2) + + vote3_dup := newVote(config, "bar:33234", "bar baz\n") + vote3_dup.expiresAt = time.Now().Add(1 * time.Hour) + quorum.vote(vote3_dup) + + vote4 := newVote(config, "baz:334234", "foo bar baz\n") + vote4.expiresAt = time.Now().Add(1 * time.Hour) + quorum.vote(vote4) + + scores := quorum.score(config) + if len(scores) != 3 { + t.Errorf("Expected scores to be of length 3: %v", scores) + } + + if scores[0].id != "bar" || scores[0].score != 3 { + t.Errorf("Expected score[0] to be {bar,3}: %v", scores[0]) + } +} + +func TestTieScore(t *testing.T) { + addVotes := func(config config, quorum quorumMap) { + vote1 := newVote(config, "foo:334234", "foo bar baz\n") + vote1.expiresAt = time.Now().Add(1 * time.Hour) + quorum.vote(vote1) + + vote2 := newVote(config, "bar:334234", "foo bar baz\n") + vote2.expiresAt = time.Now().Add(1 * time.Hour) + quorum.vote(vote2) + + vote3 := newVote(config, "baz:334234", "foo bar baz\n") + vote3.expiresAt = time.Now().Add(1 * time.Hour) + quorum.vote(vote3) + } + + t.Run("First tie score test", func(t *testing.T) { + quorum := make(quorumMap) + // If it is a tie, the first particpant (here: "foo") will win. + config := config{Participants: []string{"foo:1234", "bar:4321", "baz:3444"}} + + addVotes(config, quorum) + scores := quorum.score(config) + + if len(scores) != 3 { + t.Errorf("Expected scores to be of length 3: %v", scores) + } + if scores[0].id != "foo" || scores[0].score != 3 { + t.Errorf("Expected score[0] to be {foo,3}: %v", scores[0]) + } + }) + + t.Run("Second tie score test", func(t *testing.T) { + quorum := make(quorumMap) + // If it is a tie, the first particpant (here: "bar") will win. + config := config{Participants: []string{"bar:1234", "foo:4321", "baz:3444"}} + + addVotes(config, quorum) + scores := quorum.score(config) + + if len(scores) != 3 { + t.Errorf("Expected scores to be of length 3: %v", scores) + } + if scores[0].id != "bar" || scores[0].score != 3 { + t.Errorf("Expected score[0] to be {bar,3}: %v", scores[0]) + } + }) +} + func TestCleanExpired(t *testing.T) { quorum := make(quorumMap) config := config{Participants: []string{"foo:1234", "bay:4321"}} - vote1 := newVote(config, "earth:334234", " foo bar baz bay\n") + vote1 := newVote(config, "foo:334234", " foo bar baz bay\n") vote1.expiresAt = time.Now().Add(1 * time.Hour) quorum.vote(vote1) - vote2 := newVote(config, "bay:334234", " foo bar baz bay\n") + vote2 := newVote(config, "bar:334234", " foo bar baz bay\n") vote2.expiresAt = time.Now() quorum.vote(vote2) |
