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 }