github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/component/runner_test.go (about)

     1  package component_test
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"testing"
     7  	"time"
     8  
     9  	"code.cloudfoundry.org/clock"
    10  	"code.cloudfoundry.org/clock/fakeclock"
    11  	"code.cloudfoundry.org/lager/lagertest"
    12  	"github.com/pf-qiu/concourse/v6/atc/component"
    13  	"github.com/pf-qiu/concourse/v6/atc/component/cmocks"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/stretchr/testify/suite"
    16  	"github.com/tedsuo/ifrit"
    17  )
    18  
    19  func TestRunner(t *testing.T) {
    20  	suite.Run(t, &RunnerSuite{
    21  		Assertions: require.New(t),
    22  	})
    23  }
    24  
    25  type RunnerSuite struct {
    26  	suite.Suite
    27  	*require.Assertions
    28  
    29  	clock *fakeclock.FakeClock
    30  }
    31  
    32  func (s *RunnerSuite) SetupTest() {
    33  	s.clock = fakeclock.NewFakeClock(time.Now())
    34  	component.Clock = s.clock
    35  }
    36  
    37  func (s *RunnerSuite) TearDownTest() {
    38  	component.Clock = clock.NewClock()
    39  }
    40  
    41  func (s *RunnerSuite) TestEndToEnd() {
    42  	interval := 30 * time.Second
    43  	componentName := "some-component"
    44  
    45  	mockComponent := new(cmocks.Component)
    46  	mockComponent.On("Name").Return(componentName)
    47  	mockComponent.On("Interval").Return(interval)
    48  
    49  	mockBus := new(cmocks.NotificationsBus)
    50  
    51  	ranPeriodically := make(chan context.Context)
    52  	ranImmediately := make(chan context.Context)
    53  
    54  	mockSchedulable := schedulable{
    55  		runPeriodically: func(ctx context.Context) {
    56  			ranPeriodically <- ctx
    57  		},
    58  		runImmediately: func(ctx context.Context) {
    59  			ranImmediately <- ctx
    60  		},
    61  	}
    62  
    63  	scheduler := &component.Runner{
    64  		Logger:      lagertest.NewTestLogger("test"),
    65  		Interval:    interval,
    66  		Component:   mockComponent,
    67  		Bus:         mockBus,
    68  		Schedulable: mockSchedulable,
    69  	}
    70  
    71  	notifications := make(chan bool, 1)
    72  
    73  	var process ifrit.Process
    74  	s.Run("listens for component notifications on start", func() {
    75  		mockBus.On("Listen", componentName).Return(notifications, nil)
    76  
    77  		process = ifrit.Background(scheduler)
    78  		select {
    79  		case <-process.Ready():
    80  		case err := <-process.Wait():
    81  			s.Failf("process exited early", "error: %s", err)
    82  		}
    83  
    84  		mockBus.AssertCalled(s.T(), "Listen", componentName)
    85  	})
    86  
    87  	defer func() {
    88  		process.Signal(os.Interrupt)
    89  		<-process.Wait()
    90  	}()
    91  
    92  	s.Run("runs periodically on component interval", func() {
    93  		s.clock.WaitForWatcherAndIncrement(interval)
    94  		<-ranPeriodically
    95  		s.Empty(ranImmediately)
    96  
    97  		s.clock.WaitForWatcherAndIncrement(interval)
    98  		<-ranPeriodically
    99  		s.Empty(ranImmediately)
   100  	})
   101  
   102  	s.Run("runs immediately on notification bus events", func() {
   103  		notifications <- true
   104  		s.Empty(ranPeriodically)
   105  		<-ranImmediately
   106  
   107  		notifications <- true
   108  		s.Empty(ranPeriodically)
   109  		<-ranImmediately
   110  	})
   111  
   112  	s.Run("notifications reset the timer to prevent doing extra work", func() {
   113  		// increment timer to just under the interval
   114  		s.clock.WaitForWatcherAndIncrement(interval - 1)
   115  
   116  		// send a notification instead
   117  		notifications <- true
   118  		s.Empty(ranPeriodically)
   119  		<-ranImmediately
   120  
   121  		// pass the remaining time
   122  		s.clock.WaitForWatcherAndIncrement(1)
   123  
   124  		// send few notifications to ensure a chance to fire the periodic timer
   125  		notifications <- true
   126  		s.Empty(ranPeriodically)
   127  		<-ranImmediately
   128  		notifications <- true
   129  		s.Empty(ranPeriodically)
   130  		<-ranImmediately
   131  
   132  		// increment the timer the full amount
   133  		s.clock.WaitForWatcherAndIncrement(interval)
   134  		<-ranPeriodically
   135  		s.Empty(ranImmediately)
   136  	})
   137  
   138  	s.Run("unlistens on exit", func() {
   139  		mockBus.On("Unlisten", componentName, notifications).Return(nil)
   140  		process.Signal(os.Interrupt)
   141  
   142  		s.NoError(<-process.Wait())
   143  		mockBus.AssertCalled(s.T(), "Unlisten", componentName, notifications)
   144  	})
   145  }
   146  
   147  type schedulable struct {
   148  	runPeriodically func(context.Context)
   149  	runImmediately  func(context.Context)
   150  }
   151  
   152  func (s schedulable) RunPeriodically(ctx context.Context) {
   153  	s.runPeriodically(ctx)
   154  }
   155  
   156  func (s schedulable) RunImmediately(ctx context.Context) {
   157  	s.runImmediately(ctx)
   158  }