github.com/koko1123/flow-go-1@v0.29.6/engine/notifier_test.go (about)

     1  package engine
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/stretchr/testify/require"
    10  	"go.uber.org/atomic"
    11  )
    12  
    13  // TestNotifier_PassByValue verifies that passing Notifier by value is safe
    14  func TestNotifier_PassByValue(t *testing.T) {
    15  	t.Parallel()
    16  	notifier := NewNotifier()
    17  
    18  	var sent sync.WaitGroup
    19  	sent.Add(1)
    20  	go func(n Notifier) {
    21  		notifier.Notify()
    22  		sent.Done()
    23  	}(notifier)
    24  	sent.Wait()
    25  
    26  	select {
    27  	case <-notifier.Channel(): // expected
    28  	default:
    29  		t.Fail()
    30  	}
    31  }
    32  
    33  // TestNotifier_NoNotificationsAtStartup verifies that Notifier is initialized
    34  // without notifications
    35  func TestNotifier_NoNotificationsInitialization(t *testing.T) {
    36  	t.Parallel()
    37  	notifier := NewNotifier()
    38  	select {
    39  	case <-notifier.Channel():
    40  		t.Fail()
    41  	default: //expected
    42  	}
    43  }
    44  
    45  // TestNotifier_ManyNotifications sends many notifications to the Notifier
    46  // and verifies that:
    47  //   - the notifier accepts them all without a notification being consumed
    48  //   - only one notification is internally stored and subsequent attempts to
    49  //     read a notification would block
    50  func TestNotifier_ManyNotifications(t *testing.T) {
    51  	t.Parallel()
    52  	notifier := NewNotifier()
    53  
    54  	var counter sync.WaitGroup
    55  	for i := 0; i < 10; i++ {
    56  		counter.Add(1)
    57  		go func() {
    58  			notifier.Notify()
    59  			counter.Done()
    60  		}()
    61  	}
    62  	counter.Wait()
    63  
    64  	// attempt to consume first notification:
    65  	// expect that one notification should be available
    66  	c := notifier.Channel()
    67  	select {
    68  	case <-c: // expected
    69  	default:
    70  		t.Fail()
    71  	}
    72  
    73  	// attempt to consume first notification
    74  	// expect that no notification is available
    75  	select {
    76  	case <-c:
    77  		t.Fail()
    78  	default: //expected
    79  	}
    80  }
    81  
    82  // TestNotifier_ManyConsumers spans many worker routines and
    83  // sends just as many notifications with small delays. We require that
    84  // all workers eventually get a notification.
    85  func TestNotifier_ManyConsumers(t *testing.T) {
    86  	singleTestRun := func(t *testing.T) {
    87  		t.Parallel()
    88  		notifier := NewNotifier()
    89  		c := notifier.Channel()
    90  
    91  		// spawn 100 worker routines to each wait for a notification
    92  		var startingWorkers sync.WaitGroup
    93  		pendingWorkers := atomic.NewInt32(100)
    94  		for i := 0; i < 100; i++ {
    95  			startingWorkers.Add(1)
    96  			go func() {
    97  				startingWorkers.Done()
    98  				<-c
    99  				pendingWorkers.Dec()
   100  			}()
   101  		}
   102  		startingWorkers.Wait()
   103  
   104  		// send 100 notifications, with small delays
   105  		for i := 0; i < 100; i++ {
   106  			notifier.Notify()
   107  			time.Sleep(100 * time.Millisecond)
   108  		}
   109  
   110  		// require that all workers got a notification
   111  		if !conditionEventuallySatisfied(func() bool { return pendingWorkers.Load() == 0 }, 3*time.Second, 100*time.Millisecond) {
   112  			require.Fail(t, "timed out", "still awaiting %d workers to get notification", pendingWorkers.Load())
   113  		}
   114  	}
   115  
   116  	for r := 0; r < 100; r++ {
   117  		t.Run(fmt.Sprintf("run %d", r), singleTestRun)
   118  	}
   119  }
   120  
   121  // TestNotifier_AllWorkProcessed spans many routines pushing work and fewer
   122  // routines consuming work. We require that all worker is eventually processed.
   123  func TestNotifier_AllWorkProcessed(t *testing.T) {
   124  	singleTestRun := func(t *testing.T) {
   125  		t.Parallel()
   126  		notifier := NewNotifier()
   127  
   128  		totalWork := int32(100)
   129  		pendingWorkQueue := make(chan struct{}, totalWork)
   130  		scheduledWork := atomic.NewInt32(0)
   131  		consumedWork := atomic.NewInt32(0)
   132  
   133  		// starts the consuming first, because if we starts the production first instead, then
   134  		// we might finish pushing all jobs, before any of our consumer has started listening
   135  		// to the queue.
   136  		var consumersAllReady sync.WaitGroup
   137  		consumersAllReady.Add(5)
   138  
   139  		// 5 routines consuming work
   140  		for i := 0; i < 5; i++ {
   141  			go func() {
   142  				consumersAllReady.Done()
   143  				for consumedWork.Load() < totalWork {
   144  					<-notifier.Channel()
   145  				L:
   146  					for {
   147  						select {
   148  						case <-pendingWorkQueue:
   149  							consumedWork.Inc()
   150  						default:
   151  							break L
   152  						}
   153  					}
   154  				}
   155  			}()
   156  		}
   157  
   158  		// wait long enough for all consumer to be ready for new notification.
   159  		consumersAllReady.Wait()
   160  
   161  		var workersAllReady sync.WaitGroup
   162  		workersAllReady.Add(10)
   163  
   164  		// 10 routines pushing work
   165  		for i := 0; i < 10; i++ {
   166  			go func() {
   167  				workersAllReady.Done()
   168  				for scheduledWork.Inc() <= totalWork {
   169  					pendingWorkQueue <- struct{}{}
   170  					notifier.Notify()
   171  				}
   172  			}()
   173  		}
   174  
   175  		// wait long enough for all workers to be started.
   176  		workersAllReady.Wait()
   177  
   178  		// require that all work is eventually consumed
   179  		if !conditionEventuallySatisfied(func() bool { return consumedWork.Load() == totalWork }, 3*time.Second, 100*time.Millisecond) {
   180  			require.Fail(t, "timed out", "only consumed %d units of work but expecting %d", consumedWork.Load(), totalWork)
   181  		}
   182  	}
   183  
   184  	for r := 0; r < 100; r++ {
   185  		t.Run(fmt.Sprintf("run %d", r), singleTestRun)
   186  	}
   187  }
   188  
   189  func conditionEventuallySatisfied(condition func() bool, waitFor time.Duration, tick time.Duration) bool {
   190  	done := make(chan struct{})
   191  
   192  	go func() {
   193  		for range time.Tick(tick) {
   194  			if condition() {
   195  				close(done)
   196  				return
   197  			}
   198  		}
   199  	}()
   200  
   201  	select {
   202  	case <-time.After(waitFor):
   203  		return false
   204  	case <-done:
   205  		return true
   206  	}
   207  }