@@ -4,12 +4,13 @@ import (
44 "context"
55 crand "crypto/rand"
66 "errors"
7+ "sync"
78 "testing"
89 "testing/synctest"
910 "time"
1011
1112 "github.com/ipfs/go-datastore"
12- "github.com/ipfs/go-datastore/sync"
13+ dssync "github.com/ipfs/go-datastore/sync"
1314 "github.com/libp2p/go-libp2p/core/crypto"
1415 "github.com/rs/zerolog"
1516 "github.com/stretchr/testify/assert"
@@ -245,7 +246,7 @@ type executorTestFixture struct {
245246func setupTestExecutor (t * testing.T , pendingLimit uint64 ) executorTestFixture {
246247 t .Helper ()
247248
248- ds := sync .MutexWrap (datastore .NewMapDatastore ())
249+ ds := dssync .MutexWrap (datastore .NewMapDatastore ())
249250 memStore := store .New (ds )
250251
251252 cacheManager , err := cache .NewManager (config .DefaultConfig (), memStore , zerolog .Nop ())
@@ -342,3 +343,86 @@ func assertProduceBlockInvariantWithTxs(t *testing.T, txs [][]byte) {
342343 err = prevState .AssertValidForNextState (header , data )
343344 require .NoError (t , err , "Produced block failed AssertValidForNextState" )
344345}
346+
347+ // stubBlockProducer records ProduceBlock call times and simulates execution by sleeping.
348+ type stubBlockProducer struct {
349+ mu sync.Mutex
350+ callTimes []time.Time
351+ execTime time.Duration
352+ }
353+
354+ func (s * stubBlockProducer ) ProduceBlock (ctx context.Context ) error {
355+ s .mu .Lock ()
356+ s .callTimes = append (s .callTimes , time .Now ())
357+ s .mu .Unlock ()
358+ timer := time .NewTimer (s .execTime )
359+ defer timer .Stop ()
360+ select {
361+ case <- timer .C :
362+ case <- ctx .Done ():
363+ }
364+ return nil
365+ }
366+
367+ func (s * stubBlockProducer ) RetrieveBatch (context.Context ) (* BatchData , error ) {
368+ panic ("unexpected call" )
369+ }
370+
371+ func (s * stubBlockProducer ) CreateBlock (context.Context , uint64 , * BatchData ) (* types.SignedHeader , * types.Data , error ) {
372+ panic ("unexpected call" )
373+ }
374+
375+ func (s * stubBlockProducer ) ApplyBlock (context.Context , types.Header , * types.Data ) (types.State , error ) {
376+ panic ("unexpected call" )
377+ }
378+
379+ func (s * stubBlockProducer ) getCalls () []time.Time {
380+ s .mu .Lock ()
381+ defer s .mu .Unlock ()
382+ out := make ([]time.Time , len (s .callTimes ))
383+ copy (out , s .callTimes )
384+ return out
385+ }
386+
387+ // TestExecutionLoop_BlockInterval verifies that the block timer accounts for
388+ // execution time so that the interval between block starts equals blockTime,
389+ // not blockTime + executionTime.
390+ func TestExecutionLoop_BlockInterval (t * testing.T ) {
391+ synctest .Test (t , func (t * testing.T ) {
392+ const (
393+ blockTime = 100 * time .Millisecond
394+ executionTime = 50 * time .Millisecond
395+ wantBlocks = 5
396+ )
397+
398+ fx := setupTestExecutor (t , 1000 )
399+ defer fx .Cancel ()
400+
401+ fx .Exec .config .Node .BlockTime = config.DurationWrapper {Duration : blockTime }
402+
403+ stub := & stubBlockProducer {execTime : executionTime }
404+ fx .Exec .SetBlockProducer (stub )
405+
406+ done := make (chan struct {})
407+ go func () {
408+ fx .Exec .executionLoop ()
409+ close (done )
410+ }()
411+
412+ // advance fake time enough for wantBlocks blocks
413+ time .Sleep (blockTime + time .Duration (wantBlocks )* blockTime )
414+
415+ fx .Cancel ()
416+ <- done
417+
418+ calls := stub .getCalls ()
419+ require .GreaterOrEqual (t , len (calls ), wantBlocks ,
420+ "expected at least %d blocks, got %d" , wantBlocks , len (calls ))
421+
422+ for i := 1 ; i < len (calls ); i ++ {
423+ interval := calls [i ].Sub (calls [i - 1 ])
424+ assert .Equal (t , blockTime , interval ,
425+ "block %d: interval between block starts should equal blockTime" , i )
426+ }
427+ })
428+ }
0 commit comments