mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 18:37:26 +08:00
* wip: conversion of hotstuff from flow into Q-oriented model * bulk of tests * remaining non-integration tests * add integration test, adjust log interface, small tweaks * further adjustments, restore full pacemaker shape * add component lifecycle management+supervisor * further refinements * resolve timeout hanging * mostly finalized state for consensus * bulk of engine swap out * lifecycle-ify most types * wiring nearly complete, missing needed hooks for proposals * plugged in, vetting message validation paths * global consensus, plugged in and verified * app shard now wired in too * do not decode empty keys.yml (#456) * remove obsolete engine.maxFrames config parameter (#454) * default to Info log level unless debug is enabled (#453) * respect config's "logging" section params, remove obsolete single-file logging (#452) * Trivial code cleanup aiming to reduce Go compiler warnings (#451) * simplify range traversal * simplify channel read for single select case * delete rand.Seed() deprecated in Go 1.20 and no-op as of Go 1.24 * simplify range traversal * simplify channel read for single select case * remove redundant type from array * simplify range traversal * simplify channel read for single select case * RC slate * finalize 2.1.0.5 * Update comments in StrictMonotonicCounter Fix comment formatting and clarify description. --------- Co-authored-by: Black Swan <3999712+blacks1ne@users.noreply.github.com>
339 lines
8.8 KiB
Go
339 lines
8.8 KiB
Go
package lifecycle_test
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"source.quilibrium.com/quilibrium/monorepo/lifecycle"
|
|
"source.quilibrium.com/quilibrium/monorepo/lifecycle/mocks"
|
|
"source.quilibrium.com/quilibrium/monorepo/lifecycle/unittest"
|
|
)
|
|
|
|
// TestAllReady tests that AllReady closes its returned Ready channel only once
|
|
// all input Component instances close their Ready channel.
|
|
func TestAllReady(t *testing.T) {
|
|
cases := []int{0, 1, 100}
|
|
for _, n := range cases {
|
|
t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) {
|
|
testAllReady(n, t)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestAllDone tests that AllDone closes its returned Done channel only once
|
|
// all input Component instances close their Done channel.
|
|
func TestAllDone(t *testing.T) {
|
|
cases := []int{0, 1, 100}
|
|
for _, n := range cases {
|
|
t.Run(fmt.Sprintf("n=%d", n), func(t *testing.T) {
|
|
testAllDone(n, t)
|
|
})
|
|
}
|
|
}
|
|
|
|
func testAllDone(n int, t *testing.T) {
|
|
components := make([]lifecycle.Component, n)
|
|
for i := 0; i < n; i++ {
|
|
c := mocks.NewComponent(t)
|
|
unittest.Componentify(&c.Mock)
|
|
components[i] = c
|
|
}
|
|
|
|
unittest.AssertClosesBefore(t, lifecycle.AllReady(components...), time.Second)
|
|
|
|
for _, component := range components {
|
|
mock := component.(*mocks.Component)
|
|
mock.AssertCalled(t, "Ready")
|
|
mock.AssertNotCalled(t, "Done")
|
|
}
|
|
}
|
|
|
|
func testAllReady(n int, t *testing.T) {
|
|
components := make([]lifecycle.Component, n)
|
|
for i := 0; i < n; i++ {
|
|
c := mocks.NewComponent(t)
|
|
unittest.Componentify(&c.Mock)
|
|
components[i] = c
|
|
}
|
|
|
|
unittest.AssertClosesBefore(t, lifecycle.AllDone(components...), time.Second)
|
|
|
|
for _, component := range components {
|
|
mock := component.(*mocks.Component)
|
|
mock.AssertCalled(t, "Done")
|
|
mock.AssertNotCalled(t, "Ready")
|
|
}
|
|
}
|
|
|
|
func TestMergeChannels(t *testing.T) {
|
|
t.Run("empty slice", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := make([]<-chan int, 0)
|
|
merged := lifecycle.MergeChannels(channels).(<-chan int)
|
|
_, ok := <-merged
|
|
assert.False(t, ok)
|
|
})
|
|
t.Run("empty array", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := []<-chan int{}
|
|
merged := lifecycle.MergeChannels(channels).(<-chan int)
|
|
_, ok := <-merged
|
|
assert.False(t, ok)
|
|
})
|
|
t.Run("nil slice", func(t *testing.T) {
|
|
t.Parallel()
|
|
var channels []<-chan int
|
|
merged := lifecycle.MergeChannels(channels).(<-chan int)
|
|
_, ok := <-merged
|
|
assert.False(t, ok)
|
|
})
|
|
t.Run("nil", func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.Panics(t, func() {
|
|
lifecycle.MergeChannels(nil)
|
|
})
|
|
})
|
|
t.Run("map", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := make(map[string]<-chan int)
|
|
assert.Panics(t, func() {
|
|
lifecycle.MergeChannels(channels)
|
|
})
|
|
})
|
|
t.Run("string", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := "abcde"
|
|
assert.Panics(t, func() {
|
|
lifecycle.MergeChannels(channels)
|
|
})
|
|
})
|
|
t.Run("array of non-channel", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := []int{1, 2, 3}
|
|
assert.Panics(t, func() {
|
|
lifecycle.MergeChannels(channels)
|
|
})
|
|
})
|
|
t.Run("send channel", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := []chan<- int{make(chan int), make(chan int)}
|
|
assert.Panics(t, func() {
|
|
lifecycle.MergeChannels(channels)
|
|
})
|
|
})
|
|
t.Run("cast returned channel to send channel", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := []<-chan int{make(<-chan int), make(<-chan int)}
|
|
_, ok := lifecycle.MergeChannels(channels).(chan int)
|
|
assert.False(t, ok)
|
|
})
|
|
t.Run("happy path", func(t *testing.T) {
|
|
t.Parallel()
|
|
channels := []chan int{make(chan int), make(chan int), make(chan int)}
|
|
merged := lifecycle.MergeChannels(channels).(<-chan int)
|
|
for i, ch := range channels {
|
|
i := i
|
|
ch := ch
|
|
go func() {
|
|
ch <- i
|
|
close(ch)
|
|
}()
|
|
}
|
|
var elements []int
|
|
for i := range merged {
|
|
elements = append(elements, i)
|
|
}
|
|
assert.ElementsMatch(t, elements, []int{0, 1, 2})
|
|
})
|
|
}
|
|
|
|
func TestWaitClosed(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
t.Run("channel closed returns nil", func(t *testing.T) {
|
|
finished := make(chan struct{})
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
err := lifecycle.WaitClosed(ctx, ch)
|
|
assert.NoError(t, err)
|
|
close(finished)
|
|
}()
|
|
close(ch)
|
|
|
|
select {
|
|
case <-finished:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("timed out")
|
|
}
|
|
})
|
|
|
|
t.Run("context cancelled returns error", func(t *testing.T) {
|
|
testCtx, testCancel := context.WithCancel(ctx)
|
|
finished := make(chan struct{})
|
|
ch := make(chan struct{})
|
|
go func() {
|
|
err := lifecycle.WaitClosed(testCtx, ch)
|
|
assert.ErrorIs(t, err, context.Canceled)
|
|
close(finished)
|
|
}()
|
|
testCancel()
|
|
|
|
select {
|
|
case <-finished:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("timed out")
|
|
}
|
|
})
|
|
|
|
t.Run("both conditions triggered returns nil", func(t *testing.T) {
|
|
// both conditions are met when WaitClosed is called. Since one is randomly selected,
|
|
// there is a 99.9% probability that each condition will be picked first at least once
|
|
// during this test.
|
|
for i := 0; i < 10; i++ {
|
|
testCtx, testCancel := context.WithCancel(ctx)
|
|
finished := make(chan struct{})
|
|
ch := make(chan struct{})
|
|
close(ch)
|
|
testCancel()
|
|
|
|
go func() {
|
|
err := lifecycle.WaitClosed(testCtx, ch)
|
|
assert.NoError(t, err)
|
|
close(finished)
|
|
}()
|
|
|
|
select {
|
|
case <-finished:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("timed out")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCheckClosed(t *testing.T) {
|
|
done := make(chan struct{})
|
|
assert.False(t, lifecycle.CheckClosed(done))
|
|
close(done)
|
|
assert.True(t, lifecycle.CheckClosed(done))
|
|
}
|
|
|
|
func TestWaitError(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
testErr := errors.New("test error channel")
|
|
t.Run("error received returns error", func(t *testing.T) {
|
|
finished := make(chan struct{})
|
|
ch := make(chan error)
|
|
|
|
go func() {
|
|
err := lifecycle.WaitError(ch, ctx.Done())
|
|
assert.ErrorIs(t, err, testErr)
|
|
close(finished)
|
|
}()
|
|
ch <- testErr
|
|
|
|
select {
|
|
case <-finished:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("timed out")
|
|
}
|
|
})
|
|
|
|
t.Run("context cancelled returns error", func(t *testing.T) {
|
|
testCtx, testCancel := context.WithCancel(ctx)
|
|
finished := make(chan struct{})
|
|
ch := make(chan error)
|
|
go func() {
|
|
err := lifecycle.WaitError(ch, testCtx.Done())
|
|
assert.NoError(t, err)
|
|
close(finished)
|
|
}()
|
|
testCancel()
|
|
|
|
select {
|
|
case <-finished:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("timed out")
|
|
}
|
|
})
|
|
|
|
t.Run("both conditions triggered returns error", func(t *testing.T) {
|
|
// both conditions are met when WaitError is called. Since one is randomly selected,
|
|
// there is a 99.9% probability that each condition will be picked first at least once
|
|
// during this test.
|
|
for i := 0; i < 10; i++ {
|
|
finished := make(chan struct{})
|
|
ch := make(chan error, 1) // buffered so we can add before starting
|
|
done := make(chan struct{})
|
|
|
|
ch <- testErr
|
|
close(done)
|
|
|
|
go func() {
|
|
err := lifecycle.WaitError(ch, done)
|
|
assert.ErrorIs(t, err, testErr)
|
|
close(finished)
|
|
}()
|
|
|
|
select {
|
|
case <-finished:
|
|
case <-time.After(100 * time.Millisecond):
|
|
t.Error("timed out")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestDetypeSlice tests that DetypeSlice returns a slice which is identical
|
|
// besides the element type information.
|
|
func TestDetypeSlice(t *testing.T) {
|
|
slice := []int{1, 2, 5, 3, 53, 1234}
|
|
detyped := lifecycle.DetypeSlice(slice)
|
|
assert.Equal(t, len(slice), len(detyped))
|
|
for i := range slice {
|
|
assert.Equal(t, slice[i], detyped[i].(int))
|
|
}
|
|
}
|
|
|
|
// TestSampleN contains a series of test cases to validate the behavior of the lifecycle.SampleN function.
|
|
// The test cases cover different scenarios:
|
|
// 1. "returns expected sample": Checks if the function returns the expected sample value when
|
|
// given a valid input.
|
|
// 2. "returns max value when sample greater than max": Verifies that the function returns the
|
|
// maximum allowed value when the calculated sample exceeds the maximum limit.
|
|
// 3. "returns 0 when n is less than or equal to 0": Asserts that the function returns 0 when
|
|
// the input 'n' is less than or equal to 0, which represents an invalid input.
|
|
func TestSampleN(t *testing.T) {
|
|
t.Run("returns expected sample", func(t *testing.T) {
|
|
n := 8
|
|
max := 5.0
|
|
percentage := .5
|
|
sample := lifecycle.SampleN(n, max, percentage)
|
|
assert.Equal(t, uint(4), sample)
|
|
})
|
|
t.Run("returns max value when sample greater than max", func(t *testing.T) {
|
|
n := 20
|
|
max := 5.0
|
|
percentage := .5
|
|
sample := lifecycle.SampleN(n, max, percentage)
|
|
assert.Equal(t, uint(max), sample)
|
|
})
|
|
t.Run("returns 0 when n is less than or equal to 0", func(t *testing.T) {
|
|
n := 0
|
|
max := 5.0
|
|
percentage := .5
|
|
sample := lifecycle.SampleN(n, max, percentage)
|
|
assert.Equal(t, uint(0), sample, "sample returned should be 0 when n == 0")
|
|
n = -1
|
|
sample = lifecycle.SampleN(n, max, percentage)
|
|
assert.Equal(t, uint(0), sample, "sample returned should be 0 when n < 0")
|
|
})
|
|
}
|