Skip to content

Commit

Permalink
unittest for election.go
Browse files Browse the repository at this point in the history
  • Loading branch information
bufrr committed Feb 27, 2023
1 parent 9cfd67e commit 926f381
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 7 deletions.
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
217 changes: 217 additions & 0 deletions consensus/election/election_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
package election

import (
"encoding/hex"
"github.com/nknorg/nkn/v2/common"
"github.com/nknorg/nkn/v2/config"
"github.com/nknorg/nkn/v2/util"
"os"
"reflect"
"sync"
"testing"
"time"
)

var neighbors []*Election

func TestMain(m *testing.M) {
neighbors = make([]*Election, 3)
for i := 0; i < 3; i++ {
elc, _ := newElection()
neighbors[i] = elc
}
os.Exit(m.Run())
}

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

elc.Start()

neighborID := "123"
err = elc.ReceiveVote(neighborID, common.EmptyUint256)
if err != nil {
t.Fatal(err)
}

elc.Stop()
result, u, f, err := elc.GetResult()
if err != nil {
t.Fatal(err)
}

check("result", result, common.EmptyUint256)
check("abs weight", u, uint32(1))
check("relative weight", f, float32(1))
}

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

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

elc.Start()

neighborIDs := make([]interface{}, 10)
for i := range neighborIDs {
neighborIDs[i] = hex.EncodeToString(util.RandomBytes(16))
}
err = elc.PrefillNeighborVotes(neighborIDs, common.MaxUint256)
if err != nil {
t.Fatal(err)
}

err = elc.ReceiveVote("123", common.EmptyUint256)
if err != nil {
t.Fatal(err)
}

nids := elc.GetNeighborIDsByVote(common.MaxUint256)
check("neighbors id", len(nids), len(neighborIDs))

nid := elc.GetNeighborIDsByVote(common.EmptyUint256)
check("neighbor id", len(nid), 1)

count := elc.NeighborVoteCount()
check("neighbor vote count", count, uint32(11))

elc.Stop()
result, u, f, err := elc.GetResult()
if err != nil {
t.Fatal(err)
}

check("result", result, common.MaxUint256)
check("abs weight", u, uint32(10))
check("relative weight", f, float32(0.8333333))
}

func TestMultiElection(t *testing.T) {
for _, e := range neighbors {
go e.testElection()
}

h := common.EmptyUint256
h[0] = 1

vote1 := vote{
neighborID: "neighbor1",
hash: h,
}
vote2 := vote{
neighborID: "neighbor2",
hash: common.MaxUint256,
}
vote3 := vote{
neighborID: "neighbor3",
hash: h,
}

neighbors[0].txVoteChan <- vote1
neighbors[1].txVoteChan <- vote2
neighbors[2].txVoteChan <- vote3

time.Sleep(1 * time.Second)

var wg sync.WaitGroup
for i := 0; i < 3; i++ {
i := i
wg.Add(1)
go func(int) {
defer wg.Done()
neighbors[i].Start()
neighbors[i].voteReceived <- struct{}{}
neighbors[i].Stop()
result, _, _, _ := neighbors[i].GetResult()
if result != h {
t.Fatal("result is not equal to h")
}
}(i)
}
wg.Done()
}

func newElection() (*Election, error) {
getWeight := func(neighborID interface{}) uint32 {
return 1
}
c := &Config{
Duration: config.ConsensusDuration / 2,
MinVotingInterval: 200 * time.Millisecond,
MaxVotingInterval: 2 * time.Second,
ChangeVoteMinRelativeWeight: 0.5,
ConsensusMinRelativeWeight: 2.0 / 3.0,
GetWeight: getWeight,
}

elc := &Election{
Config: c,
state: initialized,
voteReceived: make(chan struct{}, 1),
txVoteChan: make(chan interface{}, 3),
}
return elc, nil
}

func (election *Election) testElection() {
for {
i := 0
election.neighborVotes.Range(func(key, value any) bool {
i++
return true
})
if i == 3 {
return
}
select {
case val := <-election.txVoteChan:
v, ok := val.(vote)
if !ok {
return
}

_, ok = election.neighborVotes.LoadOrStore(v.neighborID, v.hash)
if ok {
continue
}

go func() {
for _, n := range neighbors {
if n == election {
continue
}
if n.IsStopped() {
return
}
nn := n
nn.txVoteChan <- v
}
}()

}
}
}

type vote struct {
neighborID string
hash common.Uint256
}

0 comments on commit 926f381

Please sign in to comment.