-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathnode_fsm_test.go
More file actions
136 lines (122 loc) · 5.21 KB
/
node_fsm_test.go
File metadata and controls
136 lines (122 loc) · 5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package multinode
import (
"slices"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
)
type fnMock struct{ calls int }
func (fm *fnMock) Fn() {
fm.calls++
}
func (fm *fnMock) AssertNotCalled(t *testing.T) {
assert.Equal(t, 0, fm.calls)
}
func (fm *fnMock) AssertCalled(t *testing.T) {
assert.Positive(t, fm.calls)
}
func TestUnit_Node_StateTransitions(t *testing.T) {
t.Parallel()
t.Run("setState", func(t *testing.T) {
n := newTestNode(t, testNodeOpts{rpc: nil, config: testNodeConfig{nodeIsSyncingEnabled: true}})
assert.Equal(t, nodeStateUndialed, n.State())
n.setState(nodeStateAlive)
assert.Equal(t, nodeStateAlive, n.State())
n.setState(nodeStateUndialed)
assert.Equal(t, nodeStateUndialed, n.State())
})
t.Run("transitionToAlive", func(t *testing.T) {
const destinationState = nodeStateAlive
allowedStates := []nodeState{nodeStateDialed, nodeStateInvalidChainID, nodeStateSyncing, nodeStateFinalizedStateNotAvailable}
rpc := newMockRPCClient[ID, Head](t)
testTransition(t, rpc, testNode.transitionToAlive, destinationState, allowedStates...)
})
t.Run("transitionToInSync", func(t *testing.T) {
const destinationState = nodeStateAlive
allowedStates := []nodeState{nodeStateOutOfSync, nodeStateSyncing}
rpc := newMockRPCClient[ID, Head](t)
testTransition(t, rpc, testNode.transitionToInSync, destinationState, allowedStates...)
})
t.Run("transitionToOutOfSync", func(t *testing.T) {
const destinationState = nodeStateOutOfSync
allowedStates := []nodeState{nodeStateAlive, nodeStateOutOfSync}
rpc := newMockRPCClient[ID, Head](t)
rpc.On("Close")
testTransition(t, rpc, testNode.transitionToOutOfSync, destinationState, allowedStates...)
})
t.Run("transitionToUnreachable", func(t *testing.T) {
const destinationState = nodeStateUnreachable
allowedStates := []nodeState{nodeStateUndialed, nodeStateDialed, nodeStateAlive, nodeStateOutOfSync, nodeStateInvalidChainID, nodeStateSyncing, nodeStateFinalizedStateNotAvailable}
rpc := newMockRPCClient[ID, Head](t)
rpc.On("Close")
testTransition(t, rpc, testNode.transitionToUnreachable, destinationState, allowedStates...)
})
t.Run("transitionToInvalidChain", func(t *testing.T) {
const destinationState = nodeStateInvalidChainID
allowedStates := []nodeState{nodeStateDialed, nodeStateOutOfSync, nodeStateSyncing, nodeStateFinalizedStateNotAvailable}
rpc := newMockRPCClient[ID, Head](t)
rpc.On("Close")
testTransition(t, rpc, testNode.transitionToInvalidChainID, destinationState, allowedStates...)
})
t.Run("transitionToSyncing", func(t *testing.T) {
const destinationState = nodeStateSyncing
allowedStates := []nodeState{nodeStateDialed, nodeStateOutOfSync, nodeStateInvalidChainID, nodeStateFinalizedStateNotAvailable}
rpc := newMockRPCClient[ID, Head](t)
rpc.On("Close")
testTransition(t, rpc, testNode.transitionToSyncing, destinationState, allowedStates...)
})
t.Run("transitionToSyncing panics if nodeIsSyncing is disabled", func(t *testing.T) {
rpc := newMockRPCClient[ID, Head](t)
rpc.On("Close")
node := newTestNode(t, testNodeOpts{rpc: rpc})
node.setState(nodeStateDialed)
fn := new(fnMock)
defer fn.AssertNotCalled(t)
assert.PanicsWithValue(t, "unexpected transition to nodeStateSyncing, while it's disabled", func() {
node.transitionToSyncing(fn.Fn)
})
})
t.Run("transitionToFinalizedStateNotAvailable", func(t *testing.T) {
const destinationState = nodeStateFinalizedStateNotAvailable
allowedStates := []nodeState{nodeStateAlive}
rpc := newMockRPCClient[ID, Head](t)
rpc.On("Close")
testTransition(t, rpc, testNode.transitionToFinalizedStateNotAvailable, destinationState, allowedStates...)
})
}
func testTransition(t *testing.T, rpc *mockRPCClient[ID, Head], transition func(node testNode, fn func()), destinationState nodeState, allowedStates ...nodeState) {
node := newTestNode(t, testNodeOpts{rpc: rpc, config: testNodeConfig{nodeIsSyncingEnabled: true}})
for _, allowedState := range allowedStates {
m := new(fnMock)
node.setState(allowedState)
transition(node, m.Fn)
assert.Equal(t, destinationState, node.State(), "Expected node to successfully transition from %s to %s state", allowedState, destinationState)
m.AssertCalled(t)
}
// noop on attempt to transition from Closed state
m := new(fnMock)
node.setState(nodeStateClosed)
transition(node, m.Fn)
m.AssertNotCalled(t)
assert.Equal(t, nodeStateClosed, node.State(), "Expected node to remain in closed state on transition attempt")
for _, nodeState := range allNodeStates {
if slices.Contains(allowedStates, nodeState) || nodeState == nodeStateClosed {
continue
}
m := new(fnMock)
node.setState(nodeState)
assert.Panics(t, func() {
transition(node, m.Fn)
}, "Expected transition from `%s` to `%s` to panic", nodeState, destinationState)
m.AssertNotCalled(t)
assert.Equal(t, nodeState, node.State(), "Expected node to remain in initial state on invalid transition")
}
}
func TestNodeState_String(t *testing.T) {
t.Run("Ensure all states are meaningful when converted to string", func(t *testing.T) {
for _, ns := range allNodeStates {
// ensure that string representation is not nodeState(%d)
assert.NotContains(t, ns.String(), strconv.FormatInt(int64(ns), 10), "Expected node state to have readable name")
}
})
}