1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
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"
17
"github.com/juju/juju/apiserver/presence"
18
coretesting "github.com/juju/juju/testing"
19
"github.com/juju/juju/worker"
20
"github.com/juju/juju/worker/workertest"
24
fiveSeconds = 5 * time.Second
25
almostFiveSeconds = fiveSeconds - time.Nanosecond
28
// Context exposes useful functionality to fixture tests.
29
type Context interface {
31
// WaitPinger() returns the first pinger started by the SUT that
32
// has not already been returned from this method.
33
WaitPinger() worker.Worker
35
// WaitAlarms() returns once the SUT has set (but not
36
// necessarily responded to) N alarms (e.g. calls to
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)
46
// FixtureTest is called with a Context and a running Worker.
47
type FixtureTest func(Context, *presence.Worker)
49
func NewFixture(errors ...error) *Fixture {
50
return &Fixture{errors}
53
// Fixture makes it easy to manipulate a running worker's environment
54
// and test its behaviour in response.
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...)
68
func run(c *gc.C, stub *testing.Stub, test FixtureTest) {
72
clock: coretesting.NewClock(time.Now()),
73
timeout: time.After(time.Second),
74
starts: make(chan worker.Worker, 1000),
76
defer context.checkCleanedUp()
78
worker, err := presence.New(presence.Config{
79
Identity: names.NewMachineTag("1"),
80
Start: context.startPinger,
82
RetryDelay: fiveSeconds,
84
c.Assert(err, jc.ErrorIsNil)
85
defer workertest.CleanKill(c, worker)
90
// context implements Context.
94
clock *coretesting.Clock
95
timeout <-chan time.Time
97
starts chan worker.Worker
102
// WaitPinger is part of the Context interface.
103
func (context *context) WaitPinger() worker.Worker {
104
context.c.Logf("waiting for pinger...")
106
case pinger := <-context.starts:
108
case <-context.timeout:
109
context.c.Fatalf("timed out waiting for pinger")
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++ {
119
case <-context.clock.Alarms():
120
case <-context.timeout:
121
context.c.Fatalf("timed out waiting for alarm %d", i)
126
// AdvanceClock is part of the Context interface.
127
func (context *context) AdvanceClock(d time.Duration) {
128
context.clock.Advance(d)
131
func (context *context) startPinger() (presence.Pinger, error) {
132
context.stub.AddCall("Start")
133
context.checkCleanedUp()
134
if startErr := context.stub.NextErr(); startErr != nil {
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
146
func (context *context) checkCleanedUp() {
147
context.c.Logf("checking no active current pinger")
149
defer context.mu.Unlock()
150
if context.current != nil {
151
workertest.CheckKilled(context.c, context.current)
155
// mockPinger implements presence.Pinger for the convenience of the
157
type mockPinger struct {
161
func (mock mockPinger) Stop() error {
162
return worker.Stop(mock.Worker)
165
func (mock mockPinger) Wait() error {
166
return mock.Worker.Wait()
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,
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)
186
err := config.Validate()
189
worker, err := presence.New(config)
190
if !c.Check(worker, gc.IsNil) {
191
workertest.CleanKill(c, worker)