github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/utils/unittest/cluster_state_checker.go (about) 1 package unittest 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 8 "github.com/onflow/flow-go/model/flow" 9 "github.com/onflow/flow-go/state/cluster" 10 ) 11 12 const dontCheck = -1 13 14 // ClusterStateChecker is a test utility for checking cluster state. First, prepare it 15 // with expectations about the state, using `Expect*` functions, then use 16 // `Check` to assert the expectations. 17 // 18 // Duplicates are checked automatically without setting any expectations. 19 type ClusterStateChecker struct { 20 state cluster.State 21 22 // expectations about state 23 count int 24 contains []flow.Identifier 25 omits []flow.Identifier 26 } 27 28 // NewClusterStateChecker returns a state checker for the given state. 29 func NewClusterStateChecker(state cluster.State) *ClusterStateChecker { 30 checker := &ClusterStateChecker{ 31 state: state, 32 count: dontCheck, 33 contains: nil, 34 omits: nil, 35 } 36 return checker 37 } 38 39 // ExpectTxCount adds an expectation for the total count of transactions in 40 // the cluster state. 41 func (checker *ClusterStateChecker) ExpectTxCount(n int) *ClusterStateChecker { 42 checker.count = n 43 return checker 44 } 45 46 // ExpectContainsTx adds an expectation that the given transaction exists in 47 // the cluster state. 48 func (checker *ClusterStateChecker) ExpectContainsTx(txIDs ...flow.Identifier) *ClusterStateChecker { 49 checker.contains = append(checker.contains, txIDs...) 50 return checker 51 } 52 53 // ExpectOmitsTx adds an expectation that the given transaction does not exist 54 // in the cluster state. 55 func (checker *ClusterStateChecker) ExpectOmitsTx(txIDs ...flow.Identifier) *ClusterStateChecker { 56 checker.omits = append(checker.omits, txIDs...) 57 return checker 58 } 59 60 // Assert checks all assertions against the cluster state. If any assertions 61 // fail, the test will fail. 62 func (checker *ClusterStateChecker) Assert(t *testing.T) { 63 64 // start at the state head 65 head, err := checker.state.Final().Head() 66 assert.Nil(t, err) 67 68 // track properties of the state we will later compare against expectations 69 var ( 70 count = 0 // total number of transactions 71 transactions = make(map[flow.Identifier]struct{}) // unique transactions 72 dupes []flow.Identifier // duplicate transactions 73 ) 74 75 // walk the chain state from head to genesis 76 for head.Height > 0 { 77 collection, err := checker.state.AtBlockID(head.ID()).Collection() 78 assert.Nil(t, err) 79 80 head, err = checker.state.AtBlockID(head.ParentID).Head() 81 assert.Nil(t, err) 82 83 if collection.Len() == 0 { 84 continue 85 } 86 87 for _, txID := range collection.Light().Transactions { 88 count++ 89 90 _, isDupe := transactions[txID] 91 if isDupe { 92 dupes = append(dupes, txID) 93 } 94 95 transactions[txID] = struct{}{} 96 } 97 } 98 99 // ensure there are no duplicates 100 if !assert.Len(t, dupes, 0) { 101 t.Log("found duplicates: ", dupes) 102 } 103 104 // check that all manually set expectations are true 105 if checker.count != dontCheck { 106 assert.Equal(t, checker.count, count, "unexpected total number of transactions") 107 } 108 109 for _, txID := range checker.contains { 110 _, exists := transactions[txID] 111 assert.True(t, exists, "missing expected transaction: %x", txID) 112 } 113 114 for _, txID := range checker.omits { 115 _, exists := transactions[txID] 116 assert.False(t, exists, "found unexpected transaction: %x", txID) 117 } 118 }