~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/apiserver/presence/util_test.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
        coretesting "github.com/juju/juju/testing"
 
19
        "github.com/juju/juju/worker"
 
20
        "github.com/juju/juju/worker/workertest"
 
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 environment
 
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:   coretesting.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   *coretesting.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
}