github.com/decred/dcrlnd@v0.7.6/lntest/itest/test_harness.go (about) 1 package itest 2 3 import ( 4 "bytes" 5 "context" 6 "flag" 7 "fmt" 8 "math" 9 "os" 10 "path/filepath" 11 "runtime" 12 "testing" 13 "time" 14 15 "github.com/decred/dcrd/chaincfg/chainhash" 16 "github.com/decred/dcrd/chaincfg/v3" 17 "github.com/decred/dcrd/dcrutil/v4" 18 jsonrpctypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 19 "github.com/decred/dcrd/rpcclient/v8" 20 "github.com/decred/dcrd/wire" 21 "github.com/decred/dcrlnd/lnrpc" 22 "github.com/decred/dcrlnd/lntest" 23 "github.com/decred/dcrlnd/lntest/wait" 24 "github.com/go-errors/errors" 25 "github.com/stretchr/testify/require" 26 "matheusd.com/testctx" 27 ) 28 29 var ( 30 harnessNetParams = chaincfg.SimNetParams() 31 32 // lndExecutable is the full path to the lnd binary. 33 lndExecutable = flag.String( 34 "lndexec", itestLndBinary, "full path to lnd binary", 35 ) 36 37 slowMineDelay = 20 * time.Millisecond 38 ) 39 40 const ( 41 testFeeBase = 1e+6 42 defaultCSV = lntest.DefaultCSV 43 defaultTimeout = lntest.DefaultTimeout 44 minerMempoolTimeout = lntest.MinerMempoolTimeout 45 channelOpenTimeout = lntest.ChannelOpenTimeout * 4 46 channelCloseTimeout = lntest.ChannelCloseTimeout 47 itestLndBinary = "../../dcrlnd-itest" 48 49 // anchorSize is the value of an anchor output. This MUST match what 50 // lnwallet uses. 51 anchorSize = 12060 52 noFeeLimitMAtoms = math.MaxInt64 53 54 // defaultChanAmt is the default channel capacity for channels opened 55 // for testing. This is an amount that should allow a large number of 56 // channels to be opened among the default test nodes (Alice and Bob) 57 // of the lntest harness, assuming the harness initializes those nodes 58 // with 10 outputs of 1 DCR each. 59 defaultChanAmt = dcrutil.Amount(1<<24) - 1 // 0.16 DCR 60 61 AddrTypePubkeyHash = lnrpc.AddressType_PUBKEY_HASH 62 ) 63 64 // harnessTest wraps a regular testing.T providing enhanced error detection 65 // and propagation. All error will be augmented with a full stack-trace in 66 // order to aid in debugging. Additionally, any panics caused by active 67 // test cases will also be handled and represented as fatals. 68 type harnessTest struct { 69 t *testing.T 70 71 // testCase is populated during test execution and represents the 72 // current test case. 73 testCase *testCase 74 75 // lndHarness is a reference to the current network harness. Will be 76 // nil if not yet set up. 77 lndHarness *lntest.NetworkHarness 78 } 79 80 // newHarnessTest creates a new instance of a harnessTest from a regular 81 // testing.T instance. 82 func newHarnessTest(t *testing.T, net *lntest.NetworkHarness) *harnessTest { 83 return &harnessTest{t, nil, net} 84 } 85 86 // Skipf calls the underlying testing.T's Skip method, causing the current test 87 // to be skipped. 88 func (h *harnessTest) Skipf(format string, args ...interface{}) { 89 h.t.Skipf(format, args...) 90 } 91 92 // Fatalf causes the current active test case to fail with a fatal error. All 93 // integration tests should mark test failures solely with this method due to 94 // the error stack traces it produces. 95 func (h *harnessTest) Fatalf(format string, a ...interface{}) { 96 h.t.Errorf("harnessTest.Fatal() time: %s", time.Now().Format("2006-01-02T15:04:05.999")) 97 if h.lndHarness != nil { 98 h.lndHarness.SaveProfilesPages(h.t) 99 } 100 101 stacktrace := errors.Wrap(fmt.Sprintf(format, a...), 1).ErrorStack() 102 103 if h.testCase != nil { 104 h.t.Fatalf("Failed: (%v): exited with error: \n"+ 105 "%v", h.testCase.name, stacktrace) 106 } else { 107 h.t.Fatalf("Error outside of test: %v", stacktrace) 108 } 109 } 110 111 // RunTestCase executes a harness test case. Any errors or panics will be 112 // represented as fatal. 113 func (h *harnessTest) RunTestCase(testCase *testCase) { 114 115 h.testCase = testCase 116 defer func() { 117 h.testCase = nil 118 }() 119 120 defer func() { 121 if err := recover(); err != nil { 122 description := errors.Wrap(err, 2).ErrorStack() 123 h.t.Fatalf("Failed: (%v) panicked with: \n%v", 124 h.testCase.name, description) 125 } 126 }() 127 128 testCase.test(h.lndHarness, h) 129 } 130 131 func (h *harnessTest) Logf(format string, args ...interface{}) { 132 h.t.Logf(format, args...) 133 } 134 135 func (h *harnessTest) Log(args ...interface{}) { 136 h.t.Log(args...) 137 } 138 139 func (h *harnessTest) Cleanup(f func()) { 140 h.t.Cleanup(f) 141 } 142 143 func (h *harnessTest) getLndBinary() string { 144 binary := itestLndBinary 145 lndExec := "" 146 if lndExecutable != nil && *lndExecutable != "" { 147 lndExec = *lndExecutable 148 } 149 if lndExec == "" && runtime.GOOS == "windows" { 150 // Windows (even in a bash like environment like git bash as on 151 // Travis) doesn't seem to like relative paths to exe files... 152 currentDir, err := os.Getwd() 153 if err != nil { 154 h.Fatalf("unable to get working directory: %v", err) 155 } 156 targetPath := filepath.Join(currentDir, "../../lnd-itest.exe") 157 binary, err = filepath.Abs(targetPath) 158 if err != nil { 159 h.Fatalf("unable to get absolute path: %v", err) 160 } 161 } else if lndExec != "" { 162 binary = lndExec 163 } 164 165 return binary 166 } 167 168 type testCase struct { 169 name string 170 test func(net *lntest.NetworkHarness, t *harnessTest) 171 } 172 173 // waitForTxInMempool polls until finding one transaction in the provided 174 // miner's mempool. An error is returned if *one* transaction isn't found within 175 // the given timeout. 176 func waitForTxInMempool(miner *rpcclient.Client, 177 timeout time.Duration) (*chainhash.Hash, error) { 178 179 txs, err := waitForNTxsInMempool(miner, 1, timeout) 180 if err != nil { 181 return nil, err 182 } 183 184 return txs[0], err 185 } 186 187 // waitForNTxsInMempool polls until finding the desired number of transactions 188 // in the provided miner's mempool. An error is returned if this number is not 189 // met after the given timeout. 190 func waitForNTxsInMempool(miner *rpcclient.Client, n int, 191 timeout time.Duration) ([]*chainhash.Hash, error) { 192 193 breakTimeout := time.After(timeout) 194 ticker := time.NewTicker(50 * time.Millisecond) 195 defer ticker.Stop() 196 197 var err error 198 var mempool []*chainhash.Hash 199 for { 200 select { 201 case <-breakTimeout: 202 return nil, fmt.Errorf("wanted %v, found %v txs "+ 203 "in mempool: %v", n, len(mempool), mempool) 204 case <-ticker.C: 205 mempool, err = miner.GetRawMempool(context.Background(), jsonrpctypes.GRMRegular) 206 if err != nil { 207 return nil, err 208 } 209 210 if len(mempool) == n { 211 return mempool, nil 212 } 213 } 214 } 215 } 216 217 // mineBlocks mine 'num' of blocks and check that blocks are present in 218 // node blockchain. numTxs should be set to the number of transactions 219 // (excluding the coinbase) we expect to be included in the first mined block. 220 func mineBlocksFast(t *harnessTest, net *lntest.NetworkHarness, 221 num uint32, numTxs int) []*wire.MsgBlock { 222 223 // If we expect transactions to be included in the blocks we'll mine, 224 // we wait here until they are seen in the miner's mempool. 225 var txids []*chainhash.Hash 226 var err error 227 if numTxs > 0 { 228 txids, err = waitForNTxsInMempool( 229 net.Miner.Node, numTxs, minerMempoolTimeout, 230 ) 231 if err != nil { 232 t.Fatalf("unable to find txns in mempool: %v", err) 233 } 234 } 235 236 blocks := make([]*wire.MsgBlock, num) 237 238 blockHashes, err := net.Generate(num) 239 require.NoError(t.t, err) 240 241 for i, blockHash := range blockHashes { 242 block, err := net.Miner.Node.GetBlock(testctx.New(t), blockHash) 243 if err != nil { 244 t.Fatalf("unable to get block: %v", err) 245 } 246 247 blocks[i] = block 248 } 249 250 // Finally, assert that all the transactions were included in the first 251 // block. 252 for _, txid := range txids { 253 assertTxInBlock(t, blocks[0], txid) 254 } 255 256 return blocks 257 } 258 259 // mineBlocks mines 'num' of blocks and checks that blocks are present in 260 // the mining node's blockchain. numTxs should be set to the number of 261 // transactions (excluding the coinbase) we expect to be included in the first 262 // mined block. Between each mined block an artificial delay is introduced to 263 // give all network participants time to catch up. 264 // 265 // NOTE: This function currently is just an alias for mineBlocksFast. 266 func mineBlocks(t *harnessTest, net *lntest.NetworkHarness, 267 num uint32, numTxs int) []*wire.MsgBlock { 268 269 return mineBlocksFast(t, net, num, numTxs) 270 } 271 272 // mineBlocksSlow mines 'num' of blocks and checks that blocks are present in 273 // the mining node's blockchain. numTxs should be set to the number of 274 // transactions (excluding the coinbase) we expect to be included in the first 275 // mined block. Between each mined block an artificial delay is introduced to 276 // give all network participants time to catch up. 277 func mineBlocksSlow(t *harnessTest, net *lntest.NetworkHarness, 278 num uint32, numTxs int) []*wire.MsgBlock { 279 280 t.t.Helper() 281 282 // If we expect transactions to be included in the blocks we'll mine, 283 // we wait here until they are seen in the miner's mempool. 284 var txids []*chainhash.Hash 285 var err error 286 if numTxs > 0 { 287 txids, err = waitForNTxsInMempool( 288 net.Miner.Node, numTxs, minerMempoolTimeout, 289 ) 290 require.NoError(t.t, err, "unable to find txns in mempool") 291 } 292 293 blocks := make([]*wire.MsgBlock, num) 294 blockHashes := make([]*chainhash.Hash, 0, num) 295 296 for i := uint32(0); i < num; i++ { 297 generatedHashes, err := net.Generate(1) 298 require.NoError(t.t, err, "generate blocks") 299 blockHashes = append(blockHashes, generatedHashes...) 300 301 time.Sleep(slowMineDelay) 302 } 303 304 for i, blockHash := range blockHashes { 305 block, err := net.Miner.Node.GetBlock(testctx.New(t), blockHash) 306 require.NoError(t.t, err, "get blocks") 307 308 blocks[i] = block 309 } 310 311 // Finally, assert that all the transactions were included in the first 312 // block. 313 for _, txid := range txids { 314 assertTxInBlock(t, blocks[0], txid) 315 } 316 317 return blocks 318 } 319 320 func assertTxInBlock(t *harnessTest, block *wire.MsgBlock, txid *chainhash.Hash) { 321 for _, tx := range block.Transactions { 322 sha := tx.TxHash() 323 if bytes.Equal(txid[:], sha[:]) { 324 return 325 } 326 } 327 328 t.Fatalf("tx %s was not included in block", txid) 329 } 330 331 func assertWalletUnspent(t *harnessTest, node *lntest.HarnessNode, out *lnrpc.OutPoint) { 332 t.t.Helper() 333 334 err := wait.NoError(func() error { 335 ctxt, cancel := context.WithTimeout(context.Background(), defaultTimeout) 336 defer cancel() 337 unspent, err := node.ListUnspent(ctxt, &lnrpc.ListUnspentRequest{}) 338 if err != nil { 339 return err 340 } 341 342 err = errors.New("tx with wanted txhash never found") 343 for _, utxo := range unspent.Utxos { 344 if !bytes.Equal(utxo.Outpoint.TxidBytes, out.TxidBytes) { 345 continue 346 } 347 348 err = errors.New("wanted output is not a wallet utxo") 349 if utxo.Outpoint.OutputIndex != out.OutputIndex { 350 continue 351 } 352 353 return nil 354 } 355 356 t.Logf("nak %v", err) 357 358 return err 359 }, defaultTimeout) 360 if err != nil { 361 t.Fatalf("outpoint %s not unspent by %s's wallet", out, node.Name()) 362 } 363 }