github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/agent/presence/util_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package presence_test 5 6 import ( 7 "sync" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/clock/testclock" 12 "github.com/juju/errors" 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/juju/names.v2" 17 "gopkg.in/juju/worker.v1" 18 "gopkg.in/juju/worker.v1/workertest" 19 20 "github.com/juju/juju/apiserver/facades/agent/presence" 21 ) 22 23 var ( 24 fiveSeconds = 5 * time.Second 25 almostFiveSeconds = fiveSeconds - time.Nanosecond 26 ) 27 28 // Context exposes useful functionality to fixture tests. 29 type Context interface { 30 31 // WaitPinger() returns the first pinger started by the SUT that 32 // has not already been returned from this method. 33 WaitPinger() worker.Worker 34 35 // WaitAlarms() returns once the SUT has set (but not 36 // necessarily responded to) N alarms (e.g. calls to 37 // clock.After). 38 WaitAlarms(int) 39 40 // AdvanceClock() advances the SUT's clock by the duration. If 41 // you're testing alarms, be sure that you've waited for the 42 // relevant alarm to be set before you advance the clock. 43 AdvanceClock(time.Duration) 44 } 45 46 // FixtureTest is called with a Context and a running Worker. 47 type FixtureTest func(Context, *presence.Worker) 48 49 func NewFixture(errors ...error) *Fixture { 50 return &Fixture{errors} 51 } 52 53 // Fixture makes it easy to manipulate a running worker's model 54 // and test its behaviour in response. 55 type Fixture struct { 56 errors []error 57 } 58 59 // Run runs test against a fresh Stub, which is returned to the client 60 // for further analysis. 61 func (fix *Fixture) Run(c *gc.C, test FixtureTest) *testing.Stub { 62 stub := &testing.Stub{} 63 stub.SetErrors(fix.errors...) 64 run(c, stub, test) 65 return stub 66 } 67 68 func run(c *gc.C, stub *testing.Stub, test FixtureTest) { 69 context := &context{ 70 c: c, 71 stub: stub, 72 clock: testclock.NewClock(time.Now()), 73 timeout: time.After(time.Second), 74 starts: make(chan worker.Worker, 1000), 75 } 76 defer context.checkCleanedUp() 77 78 worker, err := presence.New(presence.Config{ 79 Identity: names.NewMachineTag("1"), 80 Start: context.startPinger, 81 Clock: context.clock, 82 RetryDelay: fiveSeconds, 83 }) 84 c.Assert(err, jc.ErrorIsNil) 85 defer workertest.CleanKill(c, worker) 86 87 test(context, worker) 88 } 89 90 // context implements Context. 91 type context struct { 92 c *gc.C 93 stub *testing.Stub 94 clock *testclock.Clock 95 timeout <-chan time.Time 96 97 starts chan worker.Worker 98 mu sync.Mutex 99 current worker.Worker 100 } 101 102 // WaitPinger is part of the Context interface. 103 func (context *context) WaitPinger() worker.Worker { 104 context.c.Logf("waiting for pinger...") 105 select { 106 case pinger := <-context.starts: 107 return pinger 108 case <-context.timeout: 109 context.c.Fatalf("timed out waiting for pinger") 110 return nil 111 } 112 } 113 114 // WaitAlarms is part of the Context interface. 115 func (context *context) WaitAlarms(count int) { 116 context.c.Logf("waiting for %d alarms...", count) 117 for i := 0; i < count; i++ { 118 select { 119 case <-context.clock.Alarms(): 120 case <-context.timeout: 121 context.c.Fatalf("timed out waiting for alarm %d", i) 122 } 123 } 124 } 125 126 // AdvanceClock is part of the Context interface. 127 func (context *context) AdvanceClock(d time.Duration) { 128 context.clock.Advance(d) 129 } 130 131 func (context *context) startPinger() (presence.Pinger, error) { 132 context.stub.AddCall("Start") 133 context.checkCleanedUp() 134 if startErr := context.stub.NextErr(); startErr != nil { 135 return nil, startErr 136 } 137 138 context.mu.Lock() 139 defer context.mu.Unlock() 140 pingerErr := context.stub.NextErr() 141 context.current = workertest.NewErrorWorker(pingerErr) 142 context.starts <- context.current 143 return mockPinger{context.current}, nil 144 } 145 146 func (context *context) checkCleanedUp() { 147 context.c.Logf("checking no active current pinger") 148 context.mu.Lock() 149 defer context.mu.Unlock() 150 if context.current != nil { 151 workertest.CheckKilled(context.c, context.current) 152 } 153 } 154 155 // mockPinger implements presence.Pinger for the convenience of the 156 // tests. 157 type mockPinger struct { 158 worker.Worker 159 } 160 161 func (mock mockPinger) Stop() error { 162 return worker.Stop(mock.Worker) 163 } 164 165 func (mock mockPinger) Wait() error { 166 return mock.Worker.Wait() 167 } 168 169 // validConfig returns a presence.Config that will validate, but fail 170 // violently if actually used for anything. 171 func validConfig() presence.Config { 172 return presence.Config{ 173 Identity: struct{ names.Tag }{}, 174 Start: func() (presence.Pinger, error) { panic("no") }, 175 Clock: struct{ clock.Clock }{}, 176 RetryDelay: time.Nanosecond, 177 } 178 } 179 180 func checkInvalid(c *gc.C, config presence.Config, message string) { 181 check := func(err error) { 182 c.Check(err, gc.ErrorMatches, message) 183 c.Check(err, jc.Satisfies, errors.IsNotValid) 184 } 185 186 err := config.Validate() 187 check(err) 188 189 worker, err := presence.New(config) 190 if !c.Check(worker, gc.IsNil) { 191 workertest.CleanKill(c, worker) 192 } 193 check(err) 194 }