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  }