mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-23 00:04:57 +02:00
general: adding the Clock interface to aid testing
This commit adds Clock and DefaultClock and moves the private invoices.testClock under the clock package while adding basic unit tests for it. Clock is an interface currently encapsulating Now() and TickAfter(). It can be added as an external dependency to any class. This way tests can stub out time.Now() or time.After(). The DefaultClock class simply returns the real time.Now() and time.After().
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
package invoices
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// testClock can be used in tests to mock time.
|
||||
type testClock struct {
|
||||
currentTime time.Time
|
||||
timeChanMap map[time.Time][]chan time.Time
|
||||
timeLock sync.Mutex
|
||||
}
|
||||
|
||||
// newTestClock returns a new test clock.
|
||||
func newTestClock(startTime time.Time) *testClock {
|
||||
return &testClock{
|
||||
currentTime: startTime,
|
||||
timeChanMap: make(map[time.Time][]chan time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// now returns the current (test) time.
|
||||
func (c *testClock) now() time.Time {
|
||||
c.timeLock.Lock()
|
||||
defer c.timeLock.Unlock()
|
||||
|
||||
return c.currentTime
|
||||
}
|
||||
|
||||
// tickAfter returns a channel that will receive a tick at the specified time.
|
||||
func (c *testClock) tickAfter(duration time.Duration) <-chan time.Time {
|
||||
c.timeLock.Lock()
|
||||
defer c.timeLock.Unlock()
|
||||
|
||||
triggerTime := c.currentTime.Add(duration)
|
||||
log.Debugf("tickAfter called: duration=%v, trigger_time=%v",
|
||||
duration, triggerTime)
|
||||
|
||||
ch := make(chan time.Time, 1)
|
||||
|
||||
// If already expired, tick immediately.
|
||||
if !triggerTime.After(c.currentTime) {
|
||||
ch <- c.currentTime
|
||||
return ch
|
||||
}
|
||||
|
||||
// Otherwise store the channel until the trigger time is there.
|
||||
chans := c.timeChanMap[triggerTime]
|
||||
chans = append(chans, ch)
|
||||
c.timeChanMap[triggerTime] = chans
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// setTime sets the (test) time and triggers tick channels when they expire.
|
||||
func (c *testClock) setTime(now time.Time) {
|
||||
c.timeLock.Lock()
|
||||
defer c.timeLock.Unlock()
|
||||
|
||||
c.currentTime = now
|
||||
remainingChans := make(map[time.Time][]chan time.Time)
|
||||
for triggerTime, chans := range c.timeChanMap {
|
||||
// If the trigger time is still in the future, keep this channel
|
||||
// in the channel map for later.
|
||||
if triggerTime.After(now) {
|
||||
remainingChans[triggerTime] = chans
|
||||
continue
|
||||
}
|
||||
|
||||
for _, c := range chans {
|
||||
c <- now
|
||||
}
|
||||
}
|
||||
|
||||
c.timeChanMap = remainingChans
|
||||
}
|
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/queue"
|
||||
@@ -62,12 +63,10 @@ type RegistryConfig struct {
|
||||
// waiting for the other set members to arrive.
|
||||
HtlcHoldDuration time.Duration
|
||||
|
||||
// Now returns the current time.
|
||||
Now func() time.Time
|
||||
|
||||
// TickAfter returns a channel that is sent on after the specified
|
||||
// duration as passed.
|
||||
TickAfter func(duration time.Duration) <-chan time.Time
|
||||
// Clock holds the clock implementation that is used to provide
|
||||
// Now() and TickAfter() and is useful to stub out the clock functions
|
||||
// during testing.
|
||||
Clock clock.Clock
|
||||
}
|
||||
|
||||
// htlcReleaseEvent describes an htlc auto-release event. It is used to release
|
||||
@@ -177,8 +176,8 @@ type invoiceEvent struct {
|
||||
// tickAt returns a channel that ticks at the specified time. If the time has
|
||||
// already passed, it will tick immediately.
|
||||
func (i *InvoiceRegistry) tickAt(t time.Time) <-chan time.Time {
|
||||
now := i.cfg.Now()
|
||||
return i.cfg.TickAfter(t.Sub(now))
|
||||
now := i.cfg.Clock.Now()
|
||||
return i.cfg.Clock.TickAfter(t.Sub(now))
|
||||
}
|
||||
|
||||
// invoiceEventLoop is the dedicated goroutine responsible for accepting
|
||||
|
@@ -587,7 +587,7 @@ func TestSettleMpp(t *testing.T) {
|
||||
}
|
||||
|
||||
// Simulate mpp timeout releasing htlc 1.
|
||||
ctx.clock.setTime(testTime.Add(30 * time.Second))
|
||||
ctx.clock.SetTime(testTime.Add(30 * time.Second))
|
||||
|
||||
hodlEvent := (<-hodlChan1).(HodlEvent)
|
||||
if hodlEvent.Preimage != nil {
|
||||
|
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/clock"
|
||||
"github.com/lightningnetwork/lnd/lntypes"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
@@ -120,27 +121,26 @@ func newTestChannelDB() (*channeldb.DB, func(), error) {
|
||||
|
||||
type testContext struct {
|
||||
registry *InvoiceRegistry
|
||||
clock *testClock
|
||||
clock *clock.TestClock
|
||||
|
||||
cleanup func()
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func newTestContext(t *testing.T) *testContext {
|
||||
clock := newTestClock(testTime)
|
||||
clock := clock.NewTestClock(testTime)
|
||||
|
||||
cdb, cleanup, err := newTestChannelDB()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cdb.Now = clock.now
|
||||
cdb.Now = clock.Now
|
||||
|
||||
// Instantiate and start the invoice ctx.registry.
|
||||
cfg := RegistryConfig{
|
||||
FinalCltvRejectDelta: testFinalCltvRejectDelta,
|
||||
HtlcHoldDuration: 30 * time.Second,
|
||||
Now: clock.now,
|
||||
TickAfter: clock.tickAfter,
|
||||
Clock: clock,
|
||||
}
|
||||
registry := NewRegistry(cdb, &cfg)
|
||||
|
||||
|
Reference in New Issue
Block a user