mirror of
https://github.com/QuilibriumNetwork/ceremonyclient.git
synced 2026-02-21 10:27: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>
340 lines
8.1 KiB
Go
340 lines
8.1 KiB
Go
package unittest
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"source.quilibrium.com/quilibrium/monorepo/lifecycle"
|
|
)
|
|
|
|
// MockSignalerContext is a SignalerContext that can be used in tests to assert
|
|
// that an error is thrown. It embeds a mock.Mock, so it can be used to assert
|
|
// that Throw is called with a specific error. Use
|
|
// NewMockSignalerContextExpectError to create a new MockSignalerContext that
|
|
// expects a specific error, otherwise NewMockSignalerContext.
|
|
type MockSignalerContext struct {
|
|
context.Context
|
|
*mock.Mock
|
|
}
|
|
|
|
var _ lifecycle.SignalerContext = &MockSignalerContext{}
|
|
|
|
func (m MockSignalerContext) Throw(err error) {
|
|
m.Called(err)
|
|
}
|
|
|
|
func NewMockSignalerContext(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
) *MockSignalerContext {
|
|
m := &MockSignalerContext{
|
|
Context: ctx,
|
|
Mock: &mock.Mock{},
|
|
}
|
|
m.Mock.Test(t)
|
|
t.Cleanup(func() { m.AssertExpectations(t) })
|
|
return m
|
|
}
|
|
|
|
// NewMockSignalerContextWithCancel creates a new MockSignalerContext with a
|
|
// cancel function.
|
|
func NewMockSignalerContextWithCancel(
|
|
t *testing.T,
|
|
parent context.Context,
|
|
) (*MockSignalerContext, context.CancelFunc) {
|
|
ctx, cancel := context.WithCancel(parent)
|
|
return NewMockSignalerContext(t, ctx), cancel
|
|
}
|
|
|
|
// NewMockSignalerContextExpectError creates a new MockSignalerContext which
|
|
// expects a specific error to be thrown.
|
|
func NewMockSignalerContextExpectError(
|
|
t *testing.T,
|
|
ctx context.Context,
|
|
err error,
|
|
) *MockSignalerContext {
|
|
require.NotNil(t, err)
|
|
m := NewMockSignalerContext(t, ctx)
|
|
|
|
// since we expect an error, we should expect a call to Throw
|
|
m.On("Throw", err).Once().Return()
|
|
|
|
return m
|
|
}
|
|
|
|
// AssertReturnsBefore asserts that the given function returns before the
|
|
// duration expires.
|
|
func AssertReturnsBefore(
|
|
t *testing.T,
|
|
f func(),
|
|
duration time.Duration,
|
|
msgAndArgs ...interface{},
|
|
) bool {
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
f()
|
|
close(done)
|
|
}()
|
|
|
|
select {
|
|
case <-time.After(duration):
|
|
t.Log("function did not return in time")
|
|
assert.Fail(t, "function did not close in time", msgAndArgs...)
|
|
case <-done:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ClosedChannel returns a closed channel.
|
|
func ClosedChannel() <-chan struct{} {
|
|
ch := make(chan struct{})
|
|
close(ch)
|
|
return ch
|
|
}
|
|
|
|
// AssertClosesBefore asserts that the given channel closes before the
|
|
// duration expires.
|
|
func AssertClosesBefore(
|
|
t assert.TestingT,
|
|
done <-chan struct{},
|
|
duration time.Duration,
|
|
msgAndArgs ...interface{},
|
|
) {
|
|
select {
|
|
case <-time.After(duration):
|
|
assert.Fail(t, "channel did not return in time", msgAndArgs...)
|
|
case <-done:
|
|
return
|
|
}
|
|
}
|
|
|
|
func AssertFloatEqual(t *testing.T, expected, actual float64, message string) {
|
|
tolerance := .00001
|
|
if !(math.Abs(expected-actual) < tolerance) {
|
|
assert.Equal(t, expected, actual, message)
|
|
}
|
|
}
|
|
|
|
// AssertNotClosesBefore asserts that the given channel does not close before
|
|
// the duration expires.
|
|
func AssertNotClosesBefore(
|
|
t assert.TestingT,
|
|
done <-chan struct{},
|
|
duration time.Duration,
|
|
msgAndArgs ...interface{},
|
|
) {
|
|
select {
|
|
case <-time.After(duration):
|
|
return
|
|
case <-done:
|
|
assert.Fail(t, "channel closed before timeout", msgAndArgs...)
|
|
}
|
|
}
|
|
|
|
// RequireReturnsBefore requires that the given function returns before the
|
|
// duration expires.
|
|
func RequireReturnsBefore(
|
|
t testing.TB,
|
|
f func(),
|
|
duration time.Duration,
|
|
message string,
|
|
) {
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
f()
|
|
close(done)
|
|
}()
|
|
|
|
RequireCloseBefore(
|
|
t,
|
|
done,
|
|
duration,
|
|
message+": function did not return on time",
|
|
)
|
|
}
|
|
|
|
// RequireComponentsDoneBefore invokes the done method of each of the input
|
|
// components concurrently, and fails the test if any components shutdown
|
|
// takes longer than the specified duration.
|
|
func RequireComponentsDoneBefore(
|
|
t testing.TB,
|
|
duration time.Duration,
|
|
components ...lifecycle.Component,
|
|
) {
|
|
done := lifecycle.AllDone(components...)
|
|
RequireCloseBefore(
|
|
t,
|
|
done,
|
|
duration,
|
|
"failed to shutdown all components on time",
|
|
)
|
|
}
|
|
|
|
// RequireComponentsReadyBefore invokes the ready method of each of the input
|
|
// components concurrently, and fails the test if any components startup takes
|
|
// longer than the specified duration.
|
|
func RequireComponentsReadyBefore(
|
|
t testing.TB,
|
|
duration time.Duration,
|
|
components ...lifecycle.Component,
|
|
) {
|
|
ready := lifecycle.AllReady(components...)
|
|
RequireCloseBefore(
|
|
t,
|
|
ready,
|
|
duration,
|
|
"failed to start all components on time",
|
|
)
|
|
}
|
|
|
|
// RequireCloseBefore requires that the given channel returns before the
|
|
// duration expires.
|
|
func RequireCloseBefore(
|
|
t testing.TB,
|
|
c <-chan struct{},
|
|
duration time.Duration,
|
|
message string,
|
|
) {
|
|
select {
|
|
case <-time.After(duration):
|
|
require.Fail(t, "could not close done channel on time: "+message)
|
|
case <-c:
|
|
return
|
|
}
|
|
}
|
|
|
|
// RequireClosed is a test helper function that fails the test if channel `ch`
|
|
// is not closed.
|
|
func RequireClosed(t *testing.T, ch <-chan struct{}, message string) {
|
|
select {
|
|
case <-ch:
|
|
default:
|
|
require.Fail(t, "channel is not closed: "+message)
|
|
}
|
|
}
|
|
|
|
// RequireConcurrentCallsReturnBefore is a test helper that runs function `f`
|
|
// count-many times concurrently, and requires all invocations to return within
|
|
// duration.
|
|
func RequireConcurrentCallsReturnBefore(
|
|
t *testing.T,
|
|
f func(),
|
|
count int,
|
|
duration time.Duration,
|
|
message string,
|
|
) {
|
|
wg := &sync.WaitGroup{}
|
|
for i := 0; i < count; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
f()
|
|
wg.Done()
|
|
}()
|
|
}
|
|
|
|
RequireReturnsBefore(t, wg.Wait, duration, message)
|
|
}
|
|
|
|
// RequireNeverReturnBefore is a test helper that tries invoking function `f`
|
|
// and fails the test if either:
|
|
// - function `f` is not invoked within 1 second.
|
|
// - function `f` returns before specified `duration`.
|
|
//
|
|
// It also returns a channel that is closed once the function `f` returns and
|
|
// hence its openness can evaluate return status of function `f` for intervals
|
|
// longer than duration.
|
|
func RequireNeverReturnBefore(
|
|
t *testing.T,
|
|
f func(),
|
|
duration time.Duration,
|
|
message string,
|
|
) <-chan struct{} {
|
|
ch := make(chan struct{})
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
wg.Done()
|
|
f()
|
|
close(ch)
|
|
}()
|
|
|
|
// requires function invoked within next 1 second
|
|
RequireReturnsBefore(
|
|
t,
|
|
wg.Wait,
|
|
1*time.Second,
|
|
"could not invoke the function: "+message,
|
|
)
|
|
|
|
// requires function never returns within duration
|
|
RequireNeverClosedWithin(t, ch, duration, "unexpected return: "+message)
|
|
|
|
return ch
|
|
}
|
|
|
|
// RequireNeverClosedWithin is a test helper function that fails the test if
|
|
// channel `ch` is closed before the determined duration.
|
|
func RequireNeverClosedWithin(
|
|
t *testing.T,
|
|
ch <-chan struct{},
|
|
duration time.Duration,
|
|
message string,
|
|
) {
|
|
select {
|
|
case <-time.After(duration):
|
|
case <-ch:
|
|
require.Fail(t, "channel closed before timeout: "+message)
|
|
}
|
|
}
|
|
|
|
// RequireNotClosed is a test helper function that fails the test if channel
|
|
// `ch` is closed.
|
|
func RequireNotClosed(t *testing.T, ch <-chan struct{}, message string) {
|
|
select {
|
|
case <-ch:
|
|
require.Fail(t, "channel is closed: "+message)
|
|
default:
|
|
}
|
|
}
|
|
|
|
// AssertErrSubstringMatch asserts that two errors match with substring
|
|
// checking on the Error method (`expected` must be a substring of `actual`, to
|
|
// account for the actual error being wrapped). Fails the test if either error
|
|
// is nil.
|
|
//
|
|
// NOTE: This should only be used in cases where `errors.Is` cannot be, like
|
|
// when errors are transmitted over the network without type information.
|
|
func AssertErrSubstringMatch(t testing.TB, expected, actual error) {
|
|
require.NotNil(t, expected)
|
|
require.NotNil(t, actual)
|
|
assert.True(
|
|
t,
|
|
strings.Contains(actual.Error(), expected.Error()) ||
|
|
strings.Contains(expected.Error(), actual.Error()),
|
|
"expected error: '%s', got: '%s'", expected.Error(), actual.Error(),
|
|
)
|
|
}
|
|
|
|
// Componentify sets up a generated mock to respond to Component lifecycle
|
|
// methods. Any mock type generated by mockery can be used.
|
|
func Componentify(mockable *mock.Mock) {
|
|
rwch := make(chan struct{})
|
|
var ch <-chan struct{} = rwch
|
|
close(rwch)
|
|
|
|
mockable.On("Ready").Return(ch).Maybe()
|
|
mockable.On("Done").Return(ch).Maybe()
|
|
mockable.On("Start").Return(nil).Maybe()
|
|
}
|