-
Luca Moser authoredLuca Moser authored
fpc_test.go 5.42 KiB
package fpc_test
import (
"context"
"errors"
"testing"
"github.com/iotaledger/goshimmer/packages/vote"
"github.com/iotaledger/goshimmer/packages/vote/fpc"
"github.com/iotaledger/hive.go/events"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVoteContext_IsFinalized(t *testing.T) {
type testInput struct {
voteCtx vote.Context
coolOffPeriod int
finalizationThreshold int
want bool
}
var tests = []testInput{
{vote.Context{
Opinions: []vote.Opinion{vote.Like, vote.Like, vote.Like, vote.Like, vote.Like},
}, 2, 2, true},
{vote.Context{
Opinions: []vote.Opinion{vote.Like, vote.Like, vote.Like, vote.Like, vote.Dislike},
}, 2, 2, false},
}
for _, test := range tests {
assert.Equal(t, test.want, test.voteCtx.IsFinalized(test.coolOffPeriod, test.finalizationThreshold))
}
}
func TestVoteContext_LastOpinion(t *testing.T) {
type testInput struct {
voteCtx vote.Context
expected vote.Opinion
}
var tests = []testInput{
{vote.Context{
Opinions: []vote.Opinion{vote.Like, vote.Like, vote.Like, vote.Like},
}, vote.Like},
{vote.Context{
Opinions: []vote.Opinion{vote.Like, vote.Like, vote.Like, vote.Dislike},
}, vote.Dislike},
}
for _, test := range tests {
assert.Equal(t, test.expected, test.voteCtx.LastOpinion())
}
}
func TestFPCPreventSameIDMultipleTimes(t *testing.T) {
voter := fpc.New(nil)
assert.NoError(t, voter.Vote("a", vote.Like))
// can't add the same item twice
assert.True(t, errors.Is(voter.Vote("a", vote.Like), fpc.ErrVoteAlreadyOngoing))
}
type opiniongivermock struct {
roundsReplies []vote.Opinions
roundIndex int
}
func (ogm *opiniongivermock) ID() string {
return ""
}
func (ogm *opiniongivermock) Query(_ context.Context, _ []string) (vote.Opinions, error) {
if ogm.roundIndex >= len(ogm.roundsReplies) {
return ogm.roundsReplies[len(ogm.roundsReplies)-1], nil
}
opinions := ogm.roundsReplies[ogm.roundIndex]
ogm.roundIndex++
return opinions, nil
}
func TestFPCFinalizedEvent(t *testing.T) {
opinionGiverMock := &opiniongivermock{
roundsReplies: []vote.Opinions{
// 2 cool-off period, 2 finalization threshold
{vote.Like}, {vote.Like}, {vote.Like}, {vote.Like},
},
}
opinionGiverFunc := func() (givers []vote.OpinionGiver, err error) {
return []vote.OpinionGiver{opinionGiverMock}, nil
}
id := "a"
paras := fpc.DefaultParameters()
paras.FinalizationThreshold = 2
paras.CoolingOffPeriod = 2
paras.QuerySampleSize = 1
voter := fpc.New(opinionGiverFunc, paras)
var finalizedOpinion *vote.Opinion
voter.Events().Finalized.Attach(events.NewClosure(func(id string, opinion vote.Opinion) {
finalizedOpinion = &opinion
}))
assert.NoError(t, voter.Vote(id, vote.Like))
// do 5 rounds of FPC -> 5 because the last one finalizes the vote
for i := 0; i < 5; i++ {
assert.NoError(t, voter.Round(0.5))
}
require.NotNil(t, finalizedOpinion, "finalized event should have been fired")
assert.Equal(t, vote.Like, *finalizedOpinion, "the final opinion should have been 'Like'")
}
func TestFPCFailedEvent(t *testing.T) {
opinionGiverFunc := func() (givers []vote.OpinionGiver, err error) {
return []vote.OpinionGiver{&opiniongivermock{
// doesn't matter what we set here
roundsReplies: []vote.Opinions{{vote.Dislike}},
}}, nil
}
id := "a"
paras := fpc.DefaultParameters()
paras.QuerySampleSize = 1
paras.MaxRoundsPerVoteContext = 3
paras.CoolingOffPeriod = 0
// since the finalization threshold is over max rounds it will
// always fail finalizing an opinion
paras.FinalizationThreshold = 4
voter := fpc.New(opinionGiverFunc, paras)
var failedOpinion *vote.Opinion
voter.Events().Failed.Attach(events.NewClosure(func(id string, opinion vote.Opinion) {
failedOpinion = &opinion
}))
assert.NoError(t, voter.Vote(id, vote.Like))
for i := 0; i < 4; i++ {
assert.NoError(t, voter.Round(0.5))
}
require.NotNil(t, failedOpinion, "failed event should have been fired")
assert.Equal(t, vote.Dislike, *failedOpinion, "the final opinion should have been 'Dislike'")
}
func TestFPCVotingMultipleOpinionGivers(t *testing.T) {
type testInput struct {
id string
initOpinion vote.Opinion
expectedRoundsDone int
expectedOpinion vote.Opinion
}
var tests = []testInput{
{"1", vote.Like, 5, vote.Like},
{"2", vote.Dislike, 5, vote.Dislike},
}
for _, test := range tests {
// note that even though we're defining QuerySampleSize times opinion givers,
// it doesn't mean that FPC will query all of them.
opinionGiverFunc := func() (givers []vote.OpinionGiver, err error) {
opinionGivers := make([]vote.OpinionGiver, fpc.DefaultParameters().QuerySampleSize)
for i := 0; i < len(opinionGivers); i++ {
opinionGivers[i] = &opiniongivermock{roundsReplies: []vote.Opinions{{test.initOpinion}}}
}
return opinionGivers, nil
}
paras := fpc.DefaultParameters()
paras.FinalizationThreshold = 2
paras.CoolingOffPeriod = 2
voter := fpc.New(opinionGiverFunc, paras)
var finalOpinion *vote.Opinion
voter.Events().Finalized.Attach(events.NewClosure(func(id string, finalizedOpinion vote.Opinion) {
finalOpinion = &finalizedOpinion
}))
assert.NoError(t, voter.Vote(test.id, test.initOpinion))
var roundsDone int
for finalOpinion == nil {
assert.NoError(t, voter.Round(0.7))
roundsDone++
}
assert.Equal(t, test.expectedRoundsDone, roundsDone)
require.NotNil(t, finalOpinion)
assert.Equal(t, test.expectedOpinion, *finalOpinion)
}
}