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