Skip to content

Commit

Permalink
unittest for election.go
Browse files Browse the repository at this point in the history
Signed-off-by: bufrr <chouyc.adam@gmail.com>

Signed-off-by: bufrr <chouyc.adam@gmail.com>
  • Loading branch information
bufrr committed Mar 9, 2023
1 parent 19bb78a commit 85e4ac8
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 7 deletions.
1 change: 1 addition & 0 deletions consensus/consensus_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package consensus
14 changes: 7 additions & 7 deletions consensus/election/election.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ type Election struct {
// NewElection creates an election using the config provided.
func NewElection(config *Config) (*Election, error) {
if config.Duration == 0 {
return nil, errors.New("Election duration cannot be empty")
return nil, errors.New("election duration cannot be empty")
}

if config.MinVotingInterval > config.MaxVotingInterval {
return nil, fmt.Errorf("Min voting interval %v is greater than max voting interval %v", config.MinVotingInterval, config.MaxVotingInterval)
return nil, fmt.Errorf("min voting interval %v is greater than max voting interval %v", config.MinVotingInterval, config.MaxVotingInterval)
}

if config.GetWeight == nil {
Expand All @@ -68,7 +68,7 @@ func NewElection(config *Config) (*Election, error) {
// returns error.
func (election *Election) SetInitialVote(vote interface{}) error {
if election.HasStarted() {
return errors.New("Cannot set initial vote, election has started")
return errors.New("cannot set initial vote, election has started")
}

election.Lock()
Expand Down Expand Up @@ -100,7 +100,7 @@ func (election *Election) Start() bool {
return success
}

// Stop stops an election. Typically this should not be called directly.
// Stop stops an election. Typically, this should not be called directly.
func (election *Election) Stop() {
election.Lock()
election.state = stopped
Expand Down Expand Up @@ -129,7 +129,7 @@ func (election *Election) IsStopped() bool {
// ReceiveVote receives and saves a vote from a neighbor.
func (election *Election) ReceiveVote(neighborID, vote interface{}) error {
if election.IsStopped() {
return errors.New("Election has already stopped")
return errors.New("election has already stopped")
}

election.neighborVotes.Store(neighborID, vote)
Expand All @@ -142,7 +142,7 @@ func (election *Election) ReceiveVote(neighborID, vote interface{}) error {
return nil
}

// GetTxVoteChan returns the send vote channel, which should be used to send
// GetTxVoteChan returns the sending vote channel, which should be used to send
// votes to neighbors.
func (election *Election) GetTxVoteChan() <-chan interface{} {
return election.txVoteChan
Expand Down Expand Up @@ -194,7 +194,7 @@ func (election *Election) NeighborVoteCount() uint32 {
// PrefillNeighborVotes prefills vote for neighborIDs that don't have a vote yet
func (election *Election) PrefillNeighborVotes(neighborIDs []interface{}, vote interface{}) error {
if election.IsStopped() {
return errors.New("Election has already stopped")
return errors.New("election has already stopped")
}

for _, neighborID := range neighborIDs {
Expand Down
153 changes: 153 additions & 0 deletions consensus/election/election_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package election

import (
"github.com/nknorg/nkn/v2/common"
"math"
"os"
"reflect"
"strconv"
"sync"
"testing"
"time"
)

var c = &Config{
Duration: 5 * time.Second,
MinVotingInterval: 200 * time.Millisecond,
MaxVotingInterval: 2 * time.Second,
ChangeVoteMinRelativeWeight: 0.5,
ConsensusMinRelativeWeight: 2.0 / 3.0,
}

func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func check(t *testing.T, f string, got, want interface{}) {
if !reflect.DeepEqual(got, want) {
t.Errorf("%s mismatch: got %v, want %v", f, got, want)
}
}

func TestSingleElection(t *testing.T) {
elc, err := NewElection(c)
if err != nil {
t.Fatal(err)
}

err = elc.SetInitialVote(common.MaxUint256)
if err != nil {
t.Fatal(err)
}

v1 := common.EmptyUint256
v1[0] = 255

err = elc.PrefillNeighborVotes([]interface{}{"n1", "n2", "n3"}, v1)
if err != nil {
t.Fatal(err)
}

success := elc.Start()
if !success {
t.Fatal("failed to start election")
}

txVoteChan := elc.GetTxVoteChan()

for vote := range txVoteChan {
v, ok := vote.(common.Uint256)
if !ok {
t.Fatal("unexpected vote type")
}
check(t, "vote", v, v1)
}

result, absWeight, relWeight, err := elc.GetResult()
if err != nil {
t.Fatal(err)
}

check(t, "result", result, v1)
check(t, "absWeight", absWeight, uint32(4))
tolerance := 1e-9
check(t, "relWeight", math.Abs(float64(relWeight-float32(1))) < tolerance, true)
}

func TestMultiElection(t *testing.T) {
v1 := common.EmptyUint256
v1[0] = 1

elcList := make([]*Election, 20)

var err error
for i := 0; i < len(elcList); i++ {
elcList[i], err = NewElection(c)
if err != nil {
t.Fatal(err)
}
var v common.Uint256
if i < 14 {
v = v1
} else {
v = common.MaxUint256
}
err = elcList[i].SetInitialVote(v)
if err != nil {
t.Fatal(err)
}
}
for i := 0; i < len(elcList); i++ {
for j := 0; j < len(elcList); j++ {
if i != j {
err = elcList[i].PrefillNeighborVotes([]interface{}{strconv.Itoa(j)}, elcList[j].selfVote)
if err != nil {
t.Fatal(err)
}
}
}
}

for i := 0; i < len(elcList); i++ {
go func(i int) {
success := elcList[i].Start()
if !success {
t.Error("failed to start election:", i)
return
}
}(i)
}

var wg sync.WaitGroup
for i := 0; i < len(elcList); i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
txVoteChan := elcList[i].GetTxVoteChan()
for vote := range txVoteChan {
for j, n := range elcList {
if i != j {
err = n.ReceiveVote(strconv.Itoa(i), vote)
if err != nil {
t.Error(err)
return
}
}
}
}
}(i)
}

wg.Wait()

for _, n := range elcList {
result, absWeight, relWeight, err := n.GetResult()
if err != nil {
t.Fatal(err)
}
check(t, "result", result, v1)
check(t, "absWeight", absWeight, uint32(20))
tolerance := 1e-9
check(t, "relWeight", math.Abs(float64(relWeight-float32(1))) < tolerance, true)
}
}

0 comments on commit 85e4ac8

Please sign in to comment.