From 1788fa156641228031c359f6375b8563d8fd13ce Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:48 +0200 Subject: [PATCH 01/13] rpcserver: print unconfirmed balance, add TODO for racy balance --- rpcserver.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rpcserver.go b/rpcserver.go index a98845df..4fd091b1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2099,6 +2099,8 @@ func (r *rpcServer) WalletBalance(ctx context.Context, } // Get confirmed balance, from txs that have >= 1 confirmations. + // TODO(halseth): get both unconfirmed and confirmed balance in one + // call, as this is racy. confirmedBal, err := r.server.cc.wallet.ConfirmedBalance(1) if err != nil { return nil, err @@ -2107,7 +2109,8 @@ func (r *rpcServer) WalletBalance(ctx context.Context, // Get unconfirmed balance, from txs with 0 confirmations. unconfirmedBal := totalBal - confirmedBal - rpcsLog.Debugf("[walletbalance] Total balance=%v", totalBal) + rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+ + "unconfirmed=%v)", totalBal, confirmedBal, unconfirmedBal) return &lnrpc.WalletBalanceResponse{ TotalBalance: int64(totalBal), From aec00b1277243b23983e6cae300ba1b4853a25bc Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:48 +0200 Subject: [PATCH 02/13] lntest+lnd_test: add Connect and Disconnect miner for BackendCfgs This commit gives the current chainbackend the ability to connect and disconnect the chain backend at will. We do this to let the chain backend initiate the connection to the miner, not the other way around. This is a preparation for using Neutrino as a backend, as it only allows making outbound connections. We must also move the setup of the chainbackend to after to miner, to know the address to connect to. --- lnd_test.go | 24 +++++++++--------------- lntest/btcd.go | 37 +++++++++++++++++++++++++++---------- lntest/node.go | 8 +++++--- 3 files changed, 41 insertions(+), 28 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index bf48c1bc..e014ee35 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -1763,7 +1763,6 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { var ( ctxb = context.Background() temp = "temp" - perm = "perm" ) // Set up a new miner that we can use to cause a reorg. @@ -1901,9 +1900,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { // Now we disconnect Alice's chain backend from the original miner, and // connect the two miners together. Since the temporary miner knows // about a longer chain, both miners should sync to that chain. - err = net.Miner.Node.Node( - btcjson.NRemove, net.BackendCfg.P2PAddr(), &perm, - ) + err = net.BackendCfg.DisconnectMiner() if err != nil { t.Fatalf("unable to remove node: %v", err) } @@ -1934,9 +1931,7 @@ func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to remove node: %v", err) } - err = net.Miner.Node.Node( - btcjson.NConnect, net.BackendCfg.P2PAddr(), &perm, - ) + err = net.BackendCfg.ConnectMiner() if err != nil { t.Fatalf("unable to remove node: %v", err) } @@ -13316,13 +13311,6 @@ var testsCases = []*testCase{ func TestLightningNetworkDaemon(t *testing.T) { ht := newHarnessTest(t) - // Start a btcd chain backend. - chainBackend, cleanUp, err := lntest.NewBtcdBackend() - if err != nil { - ht.Fatalf("unable to start btcd: %v", err) - } - defer cleanUp() - // Declare the network harness here to gain access to its // 'OnTxAccepted' call back. var lndHarness *lntest.NetworkHarness @@ -13343,7 +13331,6 @@ func TestLightningNetworkDaemon(t *testing.T) { "--debuglevel=debug", "--logdir=" + minerLogDir, "--trickleinterval=100ms", - "--connect=" + chainBackend.P2PAddr(), } handlers := &rpcclient.NotificationHandlers{ OnTxAccepted: func(hash *chainhash.Hash, amt btcutil.Amount) { @@ -13373,6 +13360,13 @@ func TestLightningNetworkDaemon(t *testing.T) { } }() + // Start a btcd chain backend. + chainBackend, cleanUp, err := lntest.NewBtcdBackend(miner.P2PAddress()) + if err != nil { + ht.Fatalf("unable to start btcd: %v", err) + } + defer cleanUp() + if err := miner.SetUp(true, 50); err != nil { ht.Fatalf("unable to set up mining node: %v", err) } diff --git a/lntest/btcd.go b/lntest/btcd.go index 48e4d71f..84cd6830 100644 --- a/lntest/btcd.go +++ b/lntest/btcd.go @@ -5,6 +5,7 @@ import ( "fmt" "os" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" @@ -13,14 +14,23 @@ import ( // logDir is the name of the temporary log directory. const logDir = "./.backendlogs" +// perm is used to signal we want to establish a permanent connection using the +// btcd Node API. +// +// NOTE: Cannot be const, since the node API expects a reference. +var perm = "perm" + // BtcdBackendConfig is an implementation of the BackendConfig interface // backed by a btcd node. type BtcdBackendConfig struct { // rpcConfig houses the connection config to the backing btcd instance. rpcConfig rpcclient.ConnConfig - // p2pAddress is the p2p address of the btcd instance. - p2pAddress string + // harness is the backing btcd instance. + harness *rpctest.Harness + + // minerAddr is the p2p address of the miner to connect to. + minerAddr string } // GenArgs returns the arguments needed to be passed to LND at startup for @@ -37,21 +47,27 @@ func (b BtcdBackendConfig) GenArgs() []string { return args } -// P2PAddr returns the address of this node to be used when connection over the -// Bitcoin P2P network. -func (b BtcdBackendConfig) P2PAddr() string { - return b.p2pAddress +// ConnectMiner is called to establish a connection to the test miner. +func (b BtcdBackendConfig) ConnectMiner() error { + return b.harness.Node.Node(btcjson.NConnect, b.minerAddr, &perm) +} + +// DisconnectMiner is called to disconnect the miner. +func (b BtcdBackendConfig) DisconnectMiner() error { + return b.harness.Node.Node(btcjson.NRemove, b.minerAddr, &perm) } // NewBtcdBackend starts a new rpctest.Harness and returns a BtcdBackendConfig -// for that node. -func NewBtcdBackend() (*BtcdBackendConfig, func(), error) { +// for that node. miner should be set to the P2P address of the miner to +// connect to. +func NewBtcdBackend(miner string) (*BtcdBackendConfig, func(), error) { args := []string{ "--rejectnonstd", "--txindex", "--trickleinterval=100ms", "--debuglevel=debug", "--logdir=" + logDir, + "--connect=" + miner, } netParams := &chaincfg.SimNetParams chainBackend, err := rpctest.New(netParams, nil, args) @@ -64,8 +80,9 @@ func NewBtcdBackend() (*BtcdBackendConfig, func(), error) { } bd := &BtcdBackendConfig{ - rpcConfig: chainBackend.RPCConfig(), - p2pAddress: chainBackend.P2PAddress(), + rpcConfig: chainBackend.RPCConfig(), + harness: chainBackend, + minerAddr: miner, } cleanUp := func() { diff --git a/lntest/node.go b/lntest/node.go index 3afbd5d0..8aa69e69 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -99,9 +99,11 @@ type BackendConfig interface { // for using this node as a chain backend. GenArgs() []string - // P2PAddr returns the address of this node to be used when connection - // over the Bitcoin P2P network. - P2PAddr() string + // ConnectMiner is called to establish a connection to the test miner. + ConnectMiner() error + + // DisconnectMiner is called to bitconneeeect the miner. + DisconnectMiner() error } type nodeConfig struct { From 8e8bb9ff090820cf18182a6b4176c44fce9e27f5 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:48 +0200 Subject: [PATCH 03/13] lntest+lnd_test: build flag btcd for BtcdBackendCfg --- lnd_test.go | 6 +++--- lntest/btcd.go | 10 ++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index e014ee35..fa80bc83 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -13360,10 +13360,10 @@ func TestLightningNetworkDaemon(t *testing.T) { } }() - // Start a btcd chain backend. - chainBackend, cleanUp, err := lntest.NewBtcdBackend(miner.P2PAddress()) + // Start a chain backend. + chainBackend, cleanUp, err := lntest.NewBackend(miner.P2PAddress()) if err != nil { - ht.Fatalf("unable to start btcd: %v", err) + ht.Fatalf("unable to start backend: %v", err) } defer cleanUp() diff --git a/lntest/btcd.go b/lntest/btcd.go index 84cd6830..81b22236 100644 --- a/lntest/btcd.go +++ b/lntest/btcd.go @@ -1,3 +1,5 @@ +// +build btcd + package lntest import ( @@ -57,10 +59,10 @@ func (b BtcdBackendConfig) DisconnectMiner() error { return b.harness.Node.Node(btcjson.NRemove, b.minerAddr, &perm) } -// NewBtcdBackend starts a new rpctest.Harness and returns a BtcdBackendConfig -// for that node. miner should be set to the P2P address of the miner to -// connect to. -func NewBtcdBackend(miner string) (*BtcdBackendConfig, func(), error) { +// NewBackend starts a new rpctest.Harness and returns a BtcdBackendConfig for +// that node. miner should be set to the P2P address of the miner to connect +// to. +func NewBackend(miner string) (*BtcdBackendConfig, func(), error) { args := []string{ "--rejectnonstd", "--txindex", From 3df0821aa3de806755b5e3448fe6b8b04455d428 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:48 +0200 Subject: [PATCH 04/13] make: add backend tag for itests Defaults to running itests with btcd. --- Makefile | 4 ++-- make/testing_flags.mk | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 79b7e04f..bf099bda 100644 --- a/Makefile +++ b/Makefile @@ -115,7 +115,7 @@ scratch: build check: unit itest itest-only: - @$(call print, "Running integration tests.") + @$(call print, "Running integration tests with ${backend} backend.") $(ITEST) itest: btcd build-itest itest-only @@ -148,7 +148,7 @@ travis-itest: lint itest # ============= flakehunter: build-itest - @$(call print, "Flake hunting integration tests.") + @$(call print, "Flake hunting ${backend} integration tests.") while [ $$? -eq 0 ]; do $(ITEST); done flake-unit: diff --git a/make/testing_flags.mk b/make/testing_flags.mk index eb5b43b1..49b906ed 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -55,4 +55,12 @@ endif # Construct the integration test command with the added build flags. ITEST_TAGS := $(DEV_TAGS) rpctest chainrpc walletrpc signrpc invoicesrpc autopilotrpc routerrpc + +# Default to btcd backend if not set. +ifneq ($(backend),) +ITEST_TAGS += ${backend} +else +ITEST_TAGS += btcd +endif + ITEST := rm output*.log; date; $(GOTEST) -tags="$(ITEST_TAGS)" $(TEST_FLAGS) -logoutput From 335a039ba2050b3bd332829903cb47ec395dca19 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:48 +0200 Subject: [PATCH 05/13] lnd_test: alter testUnconfirmedChannelFunding to work with Neutrino We slightly alter testUnconfirmedChannelFunding to instead of using an external deposit to test unconfirmed channel funding, we use one of our own unconfirmed change outputs. This is done since Neutrino currently has now way of knowing about incoming unconfirmed outputs. --- lnd_test.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/lnd_test.go b/lnd_test.go index fa80bc83..a1aa4ae2 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -972,8 +972,8 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) } -// testUnconfirmedChannelFunding tests that unconfirmed outputs that pay to us -// can be used to fund channels. +// testUnconfirmedChannelFunding tests that our unconfirmed change outputs can +// be used to fund channels. func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() @@ -989,13 +989,34 @@ func testUnconfirmedChannelFunding(net *lntest.NetworkHarness, t *harnessTest) { } defer shutdownAndAssert(net, t, carol) - // We'll send her some funds that should not confirm. + // We'll send her some confirmed funds. ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoinsUnconfirmed(ctxt, 2*chanAmt, carol) + err = net.SendCoins(ctxt, 2*chanAmt, carol) if err != nil { t.Fatalf("unable to send coins to carol: %v", err) } + // Now let Carol send some funds to herself, making a unconfirmed + // change output. + addrReq := &lnrpc.NewAddressRequest{ + Type: lnrpc.AddressType_WITNESS_PUBKEY_HASH, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.NewAddress(ctxt, addrReq) + if err != nil { + t.Fatalf("unable to get new address: %v", err) + } + + sendReq := &lnrpc.SendCoinsRequest{ + Addr: resp.Address, + Amount: int64(chanAmt) / 5, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = carol.SendCoins(ctxt, sendReq) + if err != nil { + t.Fatalf("unable to send coins: %v", err) + } + // Make sure the unconfirmed tx is seen in the mempool. _, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout) if err != nil { From 8f5a4beaadefafacf4f1368d3adbd393f44bfc46 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 06/13] lntest: add NeutrinoCfg --- lntest/neutrino.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 lntest/neutrino.go diff --git a/lntest/neutrino.go b/lntest/neutrino.go new file mode 100644 index 00000000..5de56825 --- /dev/null +++ b/lntest/neutrino.go @@ -0,0 +1,40 @@ +// +build neutrino + +package lntest + +import "fmt" + +// NeutrinoBackendConfig is an implementation of the BackendConfig interface +// backed by a neutrino node. +type NeutrinoBackendConfig struct { + minerAddr string +} + +// GenArgs returns the arguments needed to be passed to LND at startup for +// using this node as a chain backend. +func (b NeutrinoBackendConfig) GenArgs() []string { + var args []string + args = append(args, "--bitcoin.node=neutrino") + args = append(args, "--neutrino.connect="+b.minerAddr) + return args +} + +// ConnectMiner is called to establish a connection to the test miner. +func (b NeutrinoBackendConfig) ConnectMiner() error { + return fmt.Errorf("unimplemented") +} + +// DisconnectMiner is called to disconnect the miner. +func (b NeutrinoBackendConfig) DisconnectMiner() error { + return fmt.Errorf("unimplemented") +} + +// NewBackend starts and returns a NeutrinoBackendConfig for the node. +func NewBackend(miner string) (*NeutrinoBackendConfig, func(), error) { + bd := &NeutrinoBackendConfig{ + minerAddr: miner, + } + + cleanUp := func() {} + return bd, cleanUp, nil +} From 2d49ee56e2823d4020daf285b039f2f528cae087 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 07/13] lntest: add Name() to BackendCfg --- lntest/btcd.go | 5 +++++ lntest/neutrino.go | 5 +++++ lntest/node.go | 3 +++ 3 files changed, 13 insertions(+) diff --git a/lntest/btcd.go b/lntest/btcd.go index 81b22236..7187befd 100644 --- a/lntest/btcd.go +++ b/lntest/btcd.go @@ -59,6 +59,11 @@ func (b BtcdBackendConfig) DisconnectMiner() error { return b.harness.Node.Node(btcjson.NRemove, b.minerAddr, &perm) } +// Name returns the name of the backend type. +func (b BtcdBackendConfig) Name() string { + return "btcd" +} + // NewBackend starts a new rpctest.Harness and returns a BtcdBackendConfig for // that node. miner should be set to the P2P address of the miner to connect // to. diff --git a/lntest/neutrino.go b/lntest/neutrino.go index 5de56825..d127c0b5 100644 --- a/lntest/neutrino.go +++ b/lntest/neutrino.go @@ -29,6 +29,11 @@ func (b NeutrinoBackendConfig) DisconnectMiner() error { return fmt.Errorf("unimplemented") } +// Name returns the name of the backend type. +func (b NeutrinoBackendConfig) Name() string { + return "neutrino" +} + // NewBackend starts and returns a NeutrinoBackendConfig for the node. func NewBackend(miner string) (*NeutrinoBackendConfig, func(), error) { bd := &NeutrinoBackendConfig{ diff --git a/lntest/node.go b/lntest/node.go index 8aa69e69..751d629a 100644 --- a/lntest/node.go +++ b/lntest/node.go @@ -104,6 +104,9 @@ type BackendConfig interface { // DisconnectMiner is called to bitconneeeect the miner. DisconnectMiner() error + + // Name returns the name of the backend type. + Name() string } type nodeConfig struct { From 39d3aa6eca41046b217a49c9a22aec9384365658 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 08/13] lnd_test: add t.Skip to *harnessTest --- lnd_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lnd_test.go b/lnd_test.go index a1aa4ae2..a5753836 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -68,6 +68,12 @@ func newHarnessTest(t *testing.T) *harnessTest { return &harnessTest{t, nil} } +// Skipf calls the underlying testing.T's Skip method, causing the current test +// to be skipped. +func (h *harnessTest) Skipf(format string, args ...interface{}) { + h.t.Skipf(format, args...) +} + // Fatalf causes the current active test case to fail with a fatal error. All // integration tests should mark test failures solely with this method due to // the error stack traces it produces. From fe9ff3828e21d4b20a5315e4eafc3037609e1368 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 09/13] lnd_test: skip reorg test for neutrino backend --- lnd_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lnd_test.go b/lnd_test.go index a5753836..b0149af9 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -1787,6 +1787,13 @@ func assertMinerBlockHeightDelta(t *harnessTest, // channel where the funding tx gets reorged out, the channel will no // longer be present in the node's routing table. func testOpenChannelAfterReorg(net *lntest.NetworkHarness, t *harnessTest) { + // Skip test for neutrino, as we cannot disconnect the miner at will. + // TODO(halseth): remove when either can disconnect at will, or restart + // node with connection to new miner. + if net.BackendCfg.Name() == "neutrino" { + t.Skipf("skipping reorg test for neutrino backend") + } + var ( ctxb = context.Background() temp = "temp" From 19d6cc00e55b550983269f99699b26024217f509 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 10/13] travis: enable neutrino itest --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ce492224..501418ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ env: matrix: - RACE=true - ITEST=true + - NEUTRINO_ITEST=true - COVER=true sudo: required @@ -28,9 +29,12 @@ script: # Run unit tests with race condition detector. - 'if [ "$RACE" = true ]; then make travis-race ; fi' - # Run integration tests. + # Run btcd integration tests. - 'if [ "$ITEST" = true ]; then make travis-itest; fi' + # Run neutrino integration tests. + - 'if [ "$NEUTRINO_ITEST" = true ]; then make travis-itest backend=neutrino; fi' + # Run unit tests and generate coverage report. - 'if [ "$COVER" = true ]; then make travis-cover; fi' From ca1e9dcecb042cf42d4e4643acbb496267a0ce53 Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 11/13] lntest: skip zeroConf ListUnspent for neutrino --- lntest/harness.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lntest/harness.go b/lntest/harness.go index 4b3048e6..f2a486c8 100644 --- a/lntest/harness.go +++ b/lntest/harness.go @@ -1314,6 +1314,12 @@ func (n *NetworkHarness) sendCoins(ctx context.Context, amt btcutil.Amount, // Now, wait for ListUnspent to show the unconfirmed transaction // containing the correct pkscript. err = WaitNoError(func() error { + // Since neutrino doesn't support unconfirmed outputs, skip + // this check. + if target.cfg.BackendCfg.Name() == "neutrino" { + return nil + } + req := &lnrpc.ListUnspentRequest{} resp, err := target.ListUnspent(ctx, req) if err != nil { From 991b4174fd8ae568ff9f6161ed7ca41ce49ef89d Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 12/13] lnd_test: fix spelling --- lnd_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lnd_test.go b/lnd_test.go index b0149af9..99adba60 100644 --- a/lnd_test.go +++ b/lnd_test.go @@ -719,7 +719,7 @@ func getChanInfo(ctx context.Context, node *lntest.HarnessNode) ( } if len(channelInfo.Channels) != 1 { return nil, fmt.Errorf("node should only have a single "+ - "channel, instead he has %v", len(channelInfo.Channels)) + "channel, instead it has %v", len(channelInfo.Channels)) } return channelInfo.Channels[0], nil From 34032eff0b21f406d37d76df96292ab6a40b806a Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 24 May 2019 14:17:49 +0200 Subject: [PATCH 13/13] make: increase itest timeout to 40 min Needed for neutrino itest on darwin. --- make/testing_flags.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/testing_flags.mk b/make/testing_flags.mk index 49b906ed..032da9bf 100644 --- a/make/testing_flags.mk +++ b/make/testing_flags.mk @@ -33,7 +33,7 @@ endif ifneq ($(timeout),) TEST_FLAGS += -test.timeout=$(timeout) else -TEST_FLAGS += -test.timeout=30m +TEST_FLAGS += -test.timeout=40m endif # UNIT_TARGTED is undefined iff a specific package and/or unit test case is