Skip to content

Commit f16c98f

Browse files
committed
tapgarden: improve fault injection in unit tests
In this commit, we use atomic variables in the unit test mock interfaces to store state about requested failues. We also link requested confirmation failures to a specific conf request. Only a single failure event can be requested at a time, and the mock interfaces reset after that event. This should reduce state being unintentionally shared between test cases.
1 parent 395a180 commit f16c98f

File tree

2 files changed

+41
-24
lines changed

2 files changed

+41
-24
lines changed

tapgarden/mock.go

+35-15
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,12 @@ type MockChainBridge struct {
310310

311311
NewBlocks chan int32
312312

313-
ReqCount int
313+
ReqCount atomic.Int32
314314
ConfReqs map[int]*chainntnfs.ConfirmationEvent
315315

316316
failFeeEstimates atomic.Bool
317-
emptyConf bool
318-
errConf bool
317+
errConf atomic.Int32
318+
emptyConf atomic.Int32
319319
confErr chan error
320320
}
321321

@@ -334,19 +334,30 @@ func (m *MockChainBridge) FailFeeEstimatesOnce() {
334334
m.failFeeEstimates.Store(true)
335335
}
336336

337-
func (m *MockChainBridge) FailConf(enable bool) {
338-
m.errConf = enable
337+
// FailConfOnce updates the ChainBridge such that the next call to
338+
// RegisterConfirmationNtfn will fail by returning an error on the error channel
339+
// returned from RegisterConfirmationNtfn.
340+
func (m *MockChainBridge) FailConfOnce() {
341+
// Store the incremented request count so we never store 0 as a value.
342+
m.errConf.Store(m.ReqCount.Load() + 1)
339343
}
340-
func (m *MockChainBridge) EmptyConf(enable bool) {
341-
m.emptyConf = enable
344+
345+
// EmptyConfOnce updates the ChainBridge such that the next confirmation event
346+
// sent via SendConfNtfn will have an empty confirmation.
347+
func (m *MockChainBridge) EmptyConfOnce() {
348+
// Store the incremented request count so we never store 0 as a value.
349+
m.emptyConf.Store(m.ReqCount.Load() + 1)
342350
}
343351

344352
func (m *MockChainBridge) SendConfNtfn(reqNo int, blockHash *chainhash.Hash,
345353
blockHeight, blockIndex int, block *wire.MsgBlock,
346354
tx *wire.MsgTx) {
347355

356+
// Compare to the incremented request count since we incremented it
357+
// when storing the request number.
348358
req := m.ConfReqs[reqNo]
349-
if m.emptyConf {
359+
if m.emptyConf.Load() == int32(reqNo)+1 {
360+
m.emptyConf.Store(0)
350361
req.Confirmed <- nil
351362
return
352363
}
@@ -371,7 +382,7 @@ func (m *MockChainBridge) RegisterConfirmationsNtfn(ctx context.Context,
371382
}
372383

373384
defer func() {
374-
m.ReqCount++
385+
m.ReqCount.Add(1)
375386
}()
376387

377388
req := &chainntnfs.ConfirmationEvent{
@@ -380,15 +391,18 @@ func (m *MockChainBridge) RegisterConfirmationsNtfn(ctx context.Context,
380391
}
381392
m.confErr = make(chan error, 1)
382393

383-
m.ConfReqs[m.ReqCount] = req
394+
currentReqCount := m.ReqCount.Load()
395+
m.ConfReqs[int(currentReqCount)] = req
384396

385397
select {
386-
case m.ConfReqSignal <- m.ReqCount:
398+
case m.ConfReqSignal <- int(currentReqCount):
387399
case <-ctx.Done():
388400
}
389401

390-
if m.errConf {
391-
m.confErr <- fmt.Errorf("confirmation error")
402+
// Compare to the incremented request count since we incremented it
403+
// when storing the request number.
404+
if m.errConf.CompareAndSwap(currentReqCount+1, 0) {
405+
m.confErr <- fmt.Errorf("confirmation registration error")
392406
}
393407

394408
return req, m.confErr, nil
@@ -661,7 +675,7 @@ func (m *MockKeyRing) IsLocalKey(context.Context, keychain.KeyDescriptor) bool {
661675

662676
type MockGenSigner struct {
663677
KeyRing *MockKeyRing
664-
FailSigning bool
678+
failSigning atomic.Bool
665679
}
666680

667681
func NewMockGenSigner(keyRing *MockKeyRing) *MockGenSigner {
@@ -670,11 +684,17 @@ func NewMockGenSigner(keyRing *MockKeyRing) *MockGenSigner {
670684
}
671685
}
672686

687+
// FailSigningOnce updates the GenSigner such that the next call to
688+
// SignVirtualTx will fail by returning an error.
689+
func (m *MockGenSigner) FailSigningOnce() {
690+
m.failSigning.Store(true)
691+
}
692+
673693
func (m *MockGenSigner) SignVirtualTx(signDesc *lndclient.SignDescriptor,
674694
virtualTx *wire.MsgTx, prevOut *wire.TxOut) (*schnorr.Signature,
675695
error) {
676696

677-
if m.FailSigning {
697+
if m.failSigning.CompareAndSwap(true, false) {
678698
return nil, fmt.Errorf("failed to sign virtual tx")
679699
}
680700

tapgarden/planter_test.go

+6-9
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ func (t *mintingTestHarness) assertSeedlingsMatchSprouts(
930930
)
931931
require.NoError(t, err)
932932

933-
// Filter out any cancelled batches.
933+
// Filter out any cancelled or frozen batches.
934934
isCommittedBatch := func(batch *tapgarden.MintingBatch) bool {
935935
return batchCommittedStates.Contains(batch.State())
936936
}
@@ -947,7 +947,7 @@ func (t *mintingTestHarness) assertSeedlingsMatchSprouts(
947947
)
948948

949949
// The amount of assets committed to in the Taproot Asset commitment
950-
// should match up
950+
// should match up.
951951
dbAssets := pendingBatch.RootAssetCommitment.CommittedAssets()
952952
require.Len(t, dbAssets, len(seedlings))
953953

@@ -1147,7 +1147,7 @@ func testBasicAssetCreation(t *mintingTestHarness) {
11471147
t.assertNumCaretakersActive(1)
11481148

11491149
// We'll now force yet another restart to ensure correctness of the
1150-
// state machine, we expect the PSBT packet to still be funded.
1150+
// state machine. We expect the PSBT packet to still be funded.
11511151
t.refreshChainPlanter()
11521152
batch = t.fetchSingleBatch(nil)
11531153
t.assertBatchGenesisTx(batch.GenesisPacket)
@@ -1457,7 +1457,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
14571457
// Queue another batch, reset fee estimation behavior, and set TX
14581458
// confirmation registration to fail.
14591459
t.queueInitialBatch(numSeedlings)
1460-
t.chain.FailConf(true)
1460+
t.chain.FailConfOnce()
14611461

14621462
// Finalize the pending batch to start a caretaker, and progress the
14631463
// caretaker to TX confirmation. The finalize call should report no
@@ -1483,8 +1483,7 @@ func testFinalizeBatch(t *mintingTestHarness) {
14831483
// Queue another batch, set TX confirmation to succeed, and set the
14841484
// confirmation event to be empty.
14851485
t.queueInitialBatch(numSeedlings)
1486-
t.chain.FailConf(false)
1487-
t.chain.EmptyConf(true)
1486+
t.chain.EmptyConfOnce()
14881487

14891488
// Start a new caretaker that should reach TX broadcast.
14901489
t.finalizeBatch(&wg, respChan, nil)
@@ -1515,7 +1514,6 @@ func testFinalizeBatch(t *mintingTestHarness) {
15151514

15161515
// Queue another batch and drive the caretaker to a successful minting.
15171516
t.queueInitialBatch(numSeedlings)
1518-
t.chain.EmptyConf(false)
15191517

15201518
// Use a custom feerate and verify that the TX uses that feerate.
15211519
manualFeeRate := chainfee.FeePerKwFloor * 2
@@ -1915,7 +1913,7 @@ func testFundSealOnRestart(t *mintingTestHarness) {
19151913

19161914
// Allow batch funding to succeed, but set group key signing to fail so
19171915
// that batch sealing fails.
1918-
t.genSigner.FailSigning = true
1916+
t.genSigner.FailSigningOnce()
19191917
failedBatchCount++
19201918

19211919
// Create a seedling with emission enabled, to ensure that batch sealing
@@ -1942,7 +1940,6 @@ func testFundSealOnRestart(t *mintingTestHarness) {
19421940

19431941
// Allow batch sealing to succeed. The planter should now be able to
19441942
// start a caretaker for the batch on restart.
1945-
t.genSigner.FailSigning = false
19461943
t.queueSeedlingsInBatch(false, seedlings...)
19471944
batchCount++
19481945

0 commit comments

Comments
 (0)