summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2023-05-20 01:20:30 +0300
committerPaul Buetow <paul@buetow.org>2023-05-20 01:20:30 +0300
commit62f88b8d92b3358213f9af765422f871494ed93b (patch)
tree422546b11149185ce4dca8b45bb83ff6cfe5dd64
parent5b62dff074a0ef52c9ba1bb87ced6947d9bf6782 (diff)
can break tie score
-rw-r--r--internal/config.go11
-rw-r--r--internal/config_test.go25
-rw-r--r--internal/quorum.go11
-rw-r--r--internal/quorum_test.go82
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)