Merge lp:~chipaca/ubuntu-push/merge-automatic into lp:ubuntu-push
- merge-automatic
- Merge into trunk
Proposed by
John Lenton
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | 145 |
Merged at revision: | 142 |
Proposed branch: | lp:~chipaca/ubuntu-push/merge-automatic |
Merge into: | lp:ubuntu-push |
Diff against target: |
2417 lines (+1173/-218) 23 files modified
bus/accounts/accounts.go (+310/-0) bus/accounts/accounts_test.go (+271/-0) bus/connectivity/connectivity.go (+91/-32) bus/connectivity/connectivity_test.go (+129/-56) bus/connectivity/webchecker.go (+7/-1) bus/connectivity/webchecker_test.go (+6/-1) bus/endpoint.go (+10/-5) bus/haptic/haptic.go (+9/-2) bus/haptic/haptic_test.go (+35/-6) bus/networkmanager/networkmanager.go (+10/-10) bus/networkmanager/networkmanager_test.go (+23/-15) bus/notifications/raw.go (+1/-1) bus/notifications/raw_test.go (+7/-4) bus/testing/testing_endpoint.go (+83/-31) bus/testing/testing_endpoint_test.go (+38/-18) client/client.go (+4/-2) client/client_test.go (+12/-9) client/service/postal.go (+12/-2) client/service/postal_test.go (+17/-0) debian/changelog (+30/-18) sounds/sounds.go (+15/-2) sounds/sounds_test.go (+44/-3) testing/helpers.go (+9/-0) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/merge-automatic |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
John Lenton (community) | Approve | ||
Review via email: mp+252083@code.launchpad.net |
Commit message
The individual branches that compose this merge were peer-reviewed separately, as indicated in the commit logs.
Description of the change
To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'bus/accounts' |
2 | === added file 'bus/accounts/accounts.go' |
3 | --- bus/accounts/accounts.go 1970-01-01 00:00:00 +0000 |
4 | +++ bus/accounts/accounts.go 2015-03-06 13:26:11 +0000 |
5 | @@ -0,0 +1,310 @@ |
6 | +/* |
7 | + Copyright 2013-2015 Canonical Ltd. |
8 | + |
9 | + This program is free software: you can redistribute it and/or modify it |
10 | + under the terms of the GNU General Public License version 3, as published |
11 | + by the Free Software Foundation. |
12 | + |
13 | + This program is distributed in the hope that it will be useful, but |
14 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
15 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
16 | + PURPOSE. See the GNU General Public License for more details. |
17 | + |
18 | + You should have received a copy of the GNU General Public License along |
19 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
20 | +*/ |
21 | +// accounts exposes some properties that're stored in org.freedesktop.Accounts |
22 | +// (specifically, the ones that we need are all under |
23 | +// com.ubuntu.touch.AccountsService.Sound). |
24 | +package accounts |
25 | + |
26 | +import ( |
27 | + "fmt" |
28 | + "os/user" |
29 | + "strings" |
30 | + "sync" |
31 | + |
32 | + "launchpad.net/go-dbus/v1" |
33 | + "launchpad.net/go-xdg/v0" |
34 | + |
35 | + "launchpad.net/ubuntu-push/bus" |
36 | + "launchpad.net/ubuntu-push/logger" |
37 | +) |
38 | + |
39 | +// accounts lives on a well-known bus.Address. |
40 | +// |
41 | +// Note this one isn't it: the interface is for dbus.properties, and the path |
42 | +// is missing the UID. |
43 | +var BusAddress bus.Address = bus.Address{ |
44 | + Interface: "org.freedesktop.DBus.Properties", |
45 | + Path: "/org/freedesktop/Accounts/User", |
46 | + Name: "org.freedesktop.Accounts", |
47 | +} |
48 | + |
49 | +const accountsSoundIface = "com.ubuntu.touch.AccountsService.Sound" |
50 | + |
51 | +type Accounts interface { |
52 | + // Start() sets up the asynchronous updating of properties, and does the first update. |
53 | + Start() error |
54 | + // Cancel() stops the asynchronous updating of properties. |
55 | + Cancel() error |
56 | + // SilentMode() tells you whether the device is in silent mode. |
57 | + SilentMode() bool |
58 | + // Vibrate() tells you whether the device is allowed to vibrate. |
59 | + Vibrate() bool |
60 | + // MessageSoundFile() tells you the default sound filename. |
61 | + MessageSoundFile() string |
62 | + String() string |
63 | +} |
64 | + |
65 | +// Accounts tracks the relevant bits of configuration. Nothing directly |
66 | +// accessible because it is updated asynchronously, so use the accessors. |
67 | +type accounts struct { |
68 | + endp bus.Endpoint |
69 | + log logger.Logger |
70 | + silent bool |
71 | + vibrate bool |
72 | + vibrateSilentMode bool |
73 | + messageSound string |
74 | + cancellable bus.Cancellable |
75 | + lck sync.Mutex |
76 | + updaters map[string]func(dbus.Variant) |
77 | +} |
78 | + |
79 | +// sets up a new Accounts structure, ready to be Start()ed. |
80 | +func New(endp bus.Endpoint, log logger.Logger) Accounts { |
81 | + a := &accounts{ |
82 | + endp: endp, |
83 | + log: log, |
84 | + } |
85 | + |
86 | + a.updaters = map[string]func(dbus.Variant){ |
87 | + "SilentMode": a.updateSilentMode, |
88 | + "IncomingMessageVibrate": a.updateVibrate, |
89 | + "IncomingMessageVibrateSilentMode": a.updateVibrateSilentMode, |
90 | + "IncomingMessageSound": a.updateMessageSound, |
91 | + } |
92 | + |
93 | + return a |
94 | +} |
95 | + |
96 | +// sets up the asynchronous updating of properties, and does the first update. |
97 | +func (a *accounts) Start() error { |
98 | + err := a.startWatch() |
99 | + if err != nil { |
100 | + return err |
101 | + } |
102 | + a.update() |
103 | + return nil |
104 | +} |
105 | + |
106 | +// does sets up the watch on the PropertiesChanged signal. Separate from Start |
107 | +// because it holds a lock. |
108 | +func (a *accounts) startWatch() error { |
109 | + cancellable, err := a.endp.WatchSignal("PropertiesChanged", a.propsHandler, a.bailoutHandler) |
110 | + if err != nil { |
111 | + a.log.Errorf("unable to watch for property changes: %v", err) |
112 | + return err |
113 | + } |
114 | + |
115 | + a.lck.Lock() |
116 | + defer a.lck.Unlock() |
117 | + if a.cancellable != nil { |
118 | + panic("tried to start Accounts twice?") |
119 | + } |
120 | + a.cancellable = cancellable |
121 | + |
122 | + return nil |
123 | +} |
124 | + |
125 | +// cancel the asynchronous updating of properties. |
126 | +func (a *accounts) Cancel() error { |
127 | + return a.cancellable.Cancel() |
128 | +} |
129 | + |
130 | +// slightly shorter than %#v |
131 | +func (a *accounts) String() string { |
132 | + return fmt.Sprintf("&accounts{silent: %t, vibrate: %t, vibratesilent: %t, messageSound: %q}", |
133 | + a.silent, a.vibrate, a.vibrateSilentMode, a.messageSound) |
134 | +} |
135 | + |
136 | +// merely log that the watch loop has bailed; not much we can do. |
137 | +func (a *accounts) bailoutHandler() { |
138 | + a.log.Debugf("loop bailed out") |
139 | +} |
140 | + |
141 | +// handle PropertiesChanged, which is described in |
142 | +// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties |
143 | +func (a *accounts) propsHandler(ns ...interface{}) { |
144 | + if len(ns) != 3 { |
145 | + a.log.Errorf("PropertiesChanged delivered %d things instead of 3.", len(ns)) |
146 | + return |
147 | + } |
148 | + |
149 | + iface, ok := ns[0].(string) |
150 | + if !ok { |
151 | + a.log.Errorf("PropertiesChanged 1st param not a string: %#v.", ns[0]) |
152 | + return |
153 | + } |
154 | + if iface != accountsSoundIface { |
155 | + a.log.Debugf("PropertiesChanged for %#v, ignoring.", iface) |
156 | + return |
157 | + } |
158 | + changed, ok := ns[1].(map[interface{}]interface{}) |
159 | + if !ok { |
160 | + a.log.Errorf("PropertiesChanged 2nd param not a map: %#v.", ns[1]) |
161 | + return |
162 | + } |
163 | + if len(changed) != 0 { |
164 | + // not seen in the wild, but easy to implement properly (ie |
165 | + // using the values we're given) if it starts to |
166 | + // happen. Meanwhile just do a full update. |
167 | + a.log.Infof("PropertiesChanged provided 'changed'; reverting to full update.") |
168 | + a.update() |
169 | + return |
170 | + } |
171 | + invalid, ok := ns[2].([]interface{}) |
172 | + if !ok { |
173 | + a.log.Errorf("PropertiesChanged 3rd param not a list of properties: %#v.", ns[2]) |
174 | + return |
175 | + } |
176 | + a.log.Debugf("props changed: %#v.", invalid) |
177 | + switch len(invalid) { |
178 | + case 0: |
179 | + // nothing to do? |
180 | + a.log.Debugf("PropertiesChanged 3rd param is empty; doing nothing.") |
181 | + case 1: |
182 | + // the common case right now |
183 | + k, ok := invalid[0].(string) |
184 | + if !ok { |
185 | + a.log.Errorf("PropertiesChanged 3rd param's only entry not a string: %#v.", invalid[0]) |
186 | + return |
187 | + } |
188 | + updater, ok := a.updaters[k] |
189 | + if ok { |
190 | + var v dbus.Variant |
191 | + err := a.endp.Call("Get", []interface{}{accountsSoundIface, k}, &v) |
192 | + if err != nil { |
193 | + a.log.Errorf("when calling Get for %s: %v", k, err) |
194 | + return |
195 | + } |
196 | + a.log.Debugf("Get for %s got %#v.", k, v) |
197 | + // updaters must be called with the lock held |
198 | + a.lck.Lock() |
199 | + defer a.lck.Unlock() |
200 | + updater(v) |
201 | + a.log.Debugf("updated %s.", k) |
202 | + } |
203 | + default: |
204 | + // not seen in the wild, but we probably want to drop to a |
205 | + // full update if getting more than one change anyway. |
206 | + a.log.Infof("PropertiesChanged provided more than one 'invalid'; reverting to full update.") |
207 | + a.update() |
208 | + } |
209 | +} |
210 | + |
211 | +func (a *accounts) updateSilentMode(vsilent dbus.Variant) { |
212 | + silent, ok := vsilent.Value.(bool) |
213 | + if !ok { |
214 | + a.log.Errorf("SilentMode needed a bool.") |
215 | + return |
216 | + } |
217 | + |
218 | + a.silent = silent |
219 | +} |
220 | + |
221 | +func (a *accounts) updateVibrate(vvibrate dbus.Variant) { |
222 | + vibrate, ok := vvibrate.Value.(bool) |
223 | + if !ok { |
224 | + a.log.Errorf("IncomingMessageVibrate needed a bool.") |
225 | + return |
226 | + } |
227 | + |
228 | + a.vibrate = vibrate |
229 | +} |
230 | + |
231 | +func (a *accounts) updateVibrateSilentMode(vvibrateSilentMode dbus.Variant) { |
232 | + vibrateSilentMode, ok := vvibrateSilentMode.Value.(bool) |
233 | + if !ok { |
234 | + a.log.Errorf("IncomingMessageVibrateSilentMode needed a bool.") |
235 | + return |
236 | + } |
237 | + |
238 | + a.vibrateSilentMode = vibrateSilentMode |
239 | +} |
240 | + |
241 | +func (a *accounts) updateMessageSound(vsnd dbus.Variant) { |
242 | + snd, ok := vsnd.Value.(string) |
243 | + if !ok { |
244 | + a.log.Errorf("IncomingMessageSound needed a string.") |
245 | + return |
246 | + } |
247 | + |
248 | + for _, dir := range xdg.Data.Dirs()[1:] { |
249 | + if dir[len(dir)-1] != '/' { |
250 | + dir += "/" |
251 | + } |
252 | + if strings.HasPrefix(snd, dir) { |
253 | + snd = snd[len(dir):] |
254 | + break |
255 | + } |
256 | + } |
257 | + |
258 | + a.messageSound = snd |
259 | +} |
260 | + |
261 | +func (a *accounts) update() { |
262 | + props := make(map[string]dbus.Variant) |
263 | + err := a.endp.Call("GetAll", []interface{}{accountsSoundIface}, &props) |
264 | + if err != nil { |
265 | + a.log.Errorf("when calling GetAll: %v", err) |
266 | + return |
267 | + } |
268 | + a.log.Debugf("GetAll got: %#v", props) |
269 | + |
270 | + a.lck.Lock() |
271 | + defer a.lck.Unlock() |
272 | + |
273 | + for name, updater := range a.updaters { |
274 | + updater(props[name]) |
275 | + } |
276 | +} |
277 | + |
278 | +// is the device in silent mode? |
279 | +func (a *accounts) SilentMode() bool { |
280 | + a.lck.Lock() |
281 | + defer a.lck.Unlock() |
282 | + |
283 | + return a.silent |
284 | +} |
285 | + |
286 | +// should notifications vibrate? |
287 | +func (a *accounts) Vibrate() bool { |
288 | + a.lck.Lock() |
289 | + defer a.lck.Unlock() |
290 | + |
291 | + if a.silent { |
292 | + return a.vibrateSilentMode |
293 | + } else { |
294 | + return a.vibrate |
295 | + } |
296 | +} |
297 | + |
298 | +// what is the default sound file? |
299 | +func (a *accounts) MessageSoundFile() string { |
300 | + a.lck.Lock() |
301 | + defer a.lck.Unlock() |
302 | + |
303 | + return a.messageSound |
304 | +} |
305 | + |
306 | +// the BusAddress should actually end with the UID of the user in question; |
307 | +// here we do what's needed to get that. |
308 | +func init() { |
309 | + u, err := user.Current() |
310 | + if err != nil { |
311 | + panic(err) |
312 | + } |
313 | + |
314 | + BusAddress.Path += u.Uid |
315 | +} |
316 | |
317 | === added file 'bus/accounts/accounts_test.go' |
318 | --- bus/accounts/accounts_test.go 1970-01-01 00:00:00 +0000 |
319 | +++ bus/accounts/accounts_test.go 2015-03-06 13:26:11 +0000 |
320 | @@ -0,0 +1,271 @@ |
321 | +/* |
322 | + Copyright 2013-2015 Canonical Ltd. |
323 | + |
324 | + This program is free software: you can redistribute it and/or modify it |
325 | + under the terms of the GNU General Public License version 3, as published |
326 | + by the Free Software Foundation. |
327 | + |
328 | + This program is distributed in the hope that it will be useful, but |
329 | + WITHOUT ANY WARRANTY; without even the implied warranties of |
330 | + MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
331 | + PURPOSE. See the GNU General Public License for more details. |
332 | + |
333 | + You should have received a copy of the GNU General Public License along |
334 | + with this program. If not, see <http://www.gnu.org/licenses/>. |
335 | +*/ |
336 | + |
337 | +package accounts |
338 | + |
339 | +import ( |
340 | + "errors" |
341 | + "testing" |
342 | + |
343 | + "launchpad.net/go-dbus/v1" |
344 | + . "launchpad.net/gocheck" |
345 | + |
346 | + testibus "launchpad.net/ubuntu-push/bus/testing" |
347 | + helpers "launchpad.net/ubuntu-push/testing" |
348 | + "launchpad.net/ubuntu-push/testing/condition" |
349 | +) |
350 | + |
351 | +// hook up gocheck |
352 | +func TestAcc(t *testing.T) { TestingT(t) } |
353 | + |
354 | +type AccSuite struct { |
355 | + log *helpers.TestLogger |
356 | +} |
357 | + |
358 | +var _ = Suite(&AccSuite{}) |
359 | + |
360 | +type TestCancellable struct { |
361 | + canceled bool |
362 | + err error |
363 | +} |
364 | + |
365 | +func (t *TestCancellable) Cancel() error { |
366 | + t.canceled = true |
367 | + return t.err |
368 | +} |
369 | + |
370 | +func (s *AccSuite) SetUpTest(c *C) { |
371 | + s.log = helpers.NewTestLogger(c, "debug") |
372 | +} |
373 | + |
374 | +func (s *AccSuite) TestBusAddressPathUidLoaded(c *C) { |
375 | + c.Check(BusAddress.Path, Matches, `.*\d+`) |
376 | +} |
377 | + |
378 | +func (s *AccSuite) TestCancelCancelsCancellable(c *C) { |
379 | + err := errors.New("cancel error") |
380 | + t := &TestCancellable{err: err} |
381 | + a := New(nil, s.log).(*accounts) |
382 | + a.cancellable = t |
383 | + |
384 | + c.Check(a.Cancel(), Equals, err) |
385 | + c.Check(t.canceled, Equals, true) |
386 | +} |
387 | + |
388 | +func (s *AccSuite) TestStartReportsWatchError(c *C) { |
389 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
390 | + a := New(endp, s.log).(*accounts) |
391 | + c.Assert(a, NotNil) |
392 | + |
393 | + err := a.Start() |
394 | + c.Check(err, NotNil) |
395 | +} |
396 | + |
397 | +func (s *AccSuite) TestStartSetsCancellable(c *C) { |
398 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true) |
399 | + a := New(endp, s.log).(*accounts) |
400 | + c.Assert(a, NotNil) |
401 | + |
402 | + c.Check(a.cancellable, IsNil) |
403 | + err := a.Start() |
404 | + c.Check(err, IsNil) |
405 | + c.Check(a.cancellable, NotNil) |
406 | + a.Cancel() |
407 | +} |
408 | + |
409 | +func (s *AccSuite) TestStartPanicsIfCalledTwice(c *C) { |
410 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true, true) |
411 | + a := New(endp, s.log).(*accounts) |
412 | + c.Assert(a, NotNil) |
413 | + |
414 | + c.Check(a.cancellable, IsNil) |
415 | + err := a.Start() |
416 | + c.Check(err, IsNil) |
417 | + c.Check(func() { a.startWatch() }, PanicMatches, `.* twice\?`) |
418 | + a.Cancel() |
419 | +} |
420 | + |
421 | +func (s *AccSuite) TestUpdateCallsUpdaters(c *C) { |
422 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true), |
423 | + map[string]dbus.Variant{"x": dbus.Variant{"hello"}}) |
424 | + a := New(endp, s.log).(*accounts) |
425 | + c.Assert(a, NotNil) |
426 | + var x dbus.Variant |
427 | + a.updaters = map[string]func(dbus.Variant){ |
428 | + "x": func(v dbus.Variant) { x = v }, |
429 | + } |
430 | + a.update() |
431 | + |
432 | + c.Check(x.Value, Equals, "hello") |
433 | +} |
434 | + |
435 | +func (s *AccSuite) TestUpdateSilentModeBails(c *C) { |
436 | + a := New(nil, s.log).(*accounts) |
437 | + a.updateSilentMode(dbus.Variant{"rubbish"}) |
438 | + c.Check(s.log.Captured(), Matches, `(?ms)ERROR SilentMode needed a bool.`) |
439 | +} |
440 | + |
441 | +func (s *AccSuite) TestUpdateSilentModeWorks(c *C) { |
442 | + a := New(nil, s.log).(*accounts) |
443 | + c.Check(a.silent, Equals, false) |
444 | + a.updateSilentMode(dbus.Variant{true}) |
445 | + c.Check(a.silent, Equals, true) |
446 | +} |
447 | + |
448 | +func (s *AccSuite) TestUpdateVibrateBails(c *C) { |
449 | + a := New(nil, s.log).(*accounts) |
450 | + a.updateVibrate(dbus.Variant{"rubbish"}) |
451 | + c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrate needed a bool.`) |
452 | +} |
453 | + |
454 | +func (s *AccSuite) TestUpdateVibrateWorks(c *C) { |
455 | + a := New(nil, s.log).(*accounts) |
456 | + c.Check(a.vibrate, Equals, false) |
457 | + a.updateVibrate(dbus.Variant{true}) |
458 | + c.Check(a.vibrate, Equals, true) |
459 | +} |
460 | + |
461 | +func (s *AccSuite) TestUpdateVibrateSilentModeBails(c *C) { |
462 | + a := New(nil, s.log).(*accounts) |
463 | + a.updateVibrateSilentMode(dbus.Variant{"rubbish"}) |
464 | + c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrateSilentMode needed a bool.`) |
465 | +} |
466 | + |
467 | +func (s *AccSuite) TestUpdateVibrateSilentModeWorks(c *C) { |
468 | + a := New(nil, s.log).(*accounts) |
469 | + c.Check(a.vibrateSilentMode, Equals, false) |
470 | + a.updateVibrateSilentMode(dbus.Variant{true}) |
471 | + c.Check(a.vibrateSilentMode, Equals, true) |
472 | +} |
473 | + |
474 | +func (s *AccSuite) TestUpdateMessageSoundBails(c *C) { |
475 | + a := New(nil, s.log).(*accounts) |
476 | + a.updateMessageSound(dbus.Variant{42}) |
477 | + c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageSound needed a string.`) |
478 | +} |
479 | + |
480 | +func (s *AccSuite) TestUpdateMessageSoundWorks(c *C) { |
481 | + a := New(nil, s.log).(*accounts) |
482 | + c.Check(a.messageSound, Equals, "") |
483 | + a.updateMessageSound(dbus.Variant{"xyzzy"}) |
484 | + c.Check(a.messageSound, Equals, "xyzzy") |
485 | +} |
486 | + |
487 | +func (s *AccSuite) TestUpdateMessageSoundPrunesXDG(c *C) { |
488 | + a := New(nil, s.log).(*accounts) |
489 | + a.updateMessageSound(dbus.Variant{"/usr/share/xyzzy"}) |
490 | + c.Check(a.messageSound, Equals, "xyzzy") |
491 | +} |
492 | + |
493 | +func (s *AccSuite) TestPropsHandler(c *C) { |
494 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
495 | + |
496 | + // testing a series of bad args for propsHandler: none, |
497 | + New(endp, s.log).(*accounts).propsHandler() |
498 | + c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged delivered 0 things.*`) |
499 | + s.log.ResetCapture() |
500 | + |
501 | + // bad type for all, |
502 | + New(endp, s.log).(*accounts).propsHandler(nil, nil, nil) |
503 | + c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 1st param not a string.*`) |
504 | + s.log.ResetCapture() |
505 | + |
506 | + // wrong interface, |
507 | + New(endp, s.log).(*accounts).propsHandler("xyzzy", nil, nil) |
508 | + c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged for "xyzzy", ignoring\..*`) |
509 | + s.log.ResetCapture() |
510 | + |
511 | + // bad type for 2nd and 3rd, |
512 | + New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, nil, nil) |
513 | + c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 2nd param not a map.*`) |
514 | + s.log.ResetCapture() |
515 | + |
516 | + // not-seen-in-the-wild 'changed' argument (first non-error outcome), |
517 | + New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{"x": "y"}, nil) |
518 | + // tracking the update() via the GetAll call it generates (which will fail because of the testibus of Work(false) above) |
519 | + c.Check(s.log.Captured(), Matches, `(?ms).*INFO PropertiesChanged provided 'changed'.*ERROR when calling GetAll.*`) |
520 | + s.log.ResetCapture() |
521 | + |
522 | + // bad type for 3rd (with empty 2nd), |
523 | + New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, nil) |
524 | + c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param not a list of properties.*`) |
525 | + s.log.ResetCapture() |
526 | + |
527 | + // bad type for elements of 3rd, |
528 | + New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{42}) |
529 | + c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param's only entry not a string.*`) |
530 | + s.log.ResetCapture() |
531 | + |
532 | + // empty 3rd (not an error; hard to test "do ), |
533 | + New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{}) |
534 | + c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged 3rd param is empty.*`) |
535 | + s.log.ResetCapture() |
536 | + |
537 | + // more than one 2rd (also not an error; again looking at the GetAll failure to confirm update() got called), |
538 | + New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"hi", "there"}) |
539 | + c.Check(s.log.Captured(), Matches, `(?ms).*INFO.* reverting to full update.*ERROR when calling GetAll.*`) |
540 | + s.log.ResetCapture() |
541 | + |
542 | + // bus trouble for a single entry in the 3rd, |
543 | + New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"SilentMode"}) |
544 | + c.Check(s.log.Captured(), Matches, `(?ms).*ERROR when calling Get for SilentMode.*`) |
545 | + s.log.ResetCapture() |
546 | + |
547 | + // and finally, the common case: a single entry in the 3rd param, that gets updated individually. |
548 | + xOuter := dbus.Variant{"x"} |
549 | + a := New(testibus.NewTestingEndpoint(nil, condition.Work(true), xOuter), s.log).(*accounts) |
550 | + called := false |
551 | + a.updaters = map[string]func(dbus.Variant){"xyzzy": func(x dbus.Variant) { |
552 | + c.Check(x, Equals, xOuter) |
553 | + called = true |
554 | + }} |
555 | + a.propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"xyzzy"}) |
556 | + c.Check(called, Equals, true) |
557 | +} |
558 | + |
559 | +func (s *AccSuite) TestSilentMode(c *C) { |
560 | + a := New(nil, s.log).(*accounts) |
561 | + c.Check(a.SilentMode(), Equals, false) |
562 | + a.silent = true |
563 | + c.Check(a.SilentMode(), Equals, true) |
564 | +} |
565 | + |
566 | +func (s *AccSuite) TestVibrate(c *C) { |
567 | + a := New(nil, s.log).(*accounts) |
568 | + c.Check(a.Vibrate(), Equals, false) |
569 | + a.vibrate = true |
570 | + c.Check(a.Vibrate(), Equals, true) |
571 | + a.silent = true |
572 | + c.Check(a.Vibrate(), Equals, false) |
573 | + a.vibrateSilentMode = true |
574 | + c.Check(a.Vibrate(), Equals, true) |
575 | + a.vibrate = false |
576 | + c.Check(a.Vibrate(), Equals, true) |
577 | +} |
578 | + |
579 | +func (s *AccSuite) TestMessageSoundFile(c *C) { |
580 | + a := New(nil, s.log).(*accounts) |
581 | + c.Check(a.MessageSoundFile(), Equals, "") |
582 | + a.messageSound = "xyzzy" |
583 | + c.Check(a.MessageSoundFile(), Equals, "xyzzy") |
584 | +} |
585 | + |
586 | +func (s *AccSuite) TestString(c *C) { |
587 | + a := New(nil, s.log).(*accounts) |
588 | + a.vibrate = true |
589 | + a.messageSound = "x" |
590 | + c.Check(a.String(), Equals, `&accounts{silent: false, vibrate: true, vibratesilent: false, messageSound: "x"}`) |
591 | +} |
592 | |
593 | === modified file 'bus/connectivity/connectivity.go' |
594 | --- bus/connectivity/connectivity.go 2015-01-22 11:05:37 +0000 |
595 | +++ bus/connectivity/connectivity.go 2015-03-06 13:26:11 +0000 |
596 | @@ -1,5 +1,5 @@ |
597 | /* |
598 | - Copyright 2013-2014 Canonical Ltd. |
599 | + Copyright 2013-2015 Canonical Ltd. |
600 | |
601 | This program is free software: you can redistribute it and/or modify it |
602 | under the terms of the GNU General Public License version 3, as published |
603 | @@ -24,12 +24,14 @@ |
604 | |
605 | import ( |
606 | "errors" |
607 | + "sync" |
608 | + "time" |
609 | + |
610 | "launchpad.net/ubuntu-push/bus" |
611 | "launchpad.net/ubuntu-push/bus/networkmanager" |
612 | "launchpad.net/ubuntu-push/config" |
613 | "launchpad.net/ubuntu-push/logger" |
614 | "launchpad.net/ubuntu-push/util" |
615 | - "time" |
616 | ) |
617 | |
618 | // The configuration for ConnectedState, intended to be populated from a config file. |
619 | @@ -45,23 +47,56 @@ |
620 | ConnectivityCheckMD5 string `json:"connectivity_check_md5"` |
621 | } |
622 | |
623 | -type connectedState struct { |
624 | +// ConnectedState helps tracking connectivity. |
625 | +type ConnectedState struct { |
626 | networkStateCh <-chan networkmanager.State |
627 | networkConCh <-chan string |
628 | config ConnectivityConfig |
629 | log logger.Logger |
630 | endp bus.Endpoint |
631 | connAttempts uint32 |
632 | - webget func(ch chan<- bool) |
633 | + webchk Webchecker |
634 | webgetCh chan bool |
635 | currentState networkmanager.State |
636 | lastSent bool |
637 | timer *time.Timer |
638 | + doneLck sync.Mutex |
639 | + done chan struct{} |
640 | + canceled bool |
641 | + stateWatch bus.Cancellable |
642 | + conWatch bus.Cancellable |
643 | +} |
644 | + |
645 | +// New makes a ConnectedState for connectivity tracking. |
646 | +// |
647 | +// The endpoint need not be dialed; Track() will Dial() and |
648 | +// Close() it as it sees fit. |
649 | +func New(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger) *ConnectedState { |
650 | + wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log) |
651 | + return &ConnectedState{ |
652 | + config: config, |
653 | + log: log, |
654 | + endp: endp, |
655 | + webchk: wg, |
656 | + done: make(chan struct{}), |
657 | + } |
658 | +} |
659 | + |
660 | +// cancel watches if any |
661 | +func (cs *ConnectedState) reset() { |
662 | + if cs.stateWatch != nil { |
663 | + cs.stateWatch.Cancel() |
664 | + cs.stateWatch = nil |
665 | + } |
666 | + if cs.conWatch != nil { |
667 | + cs.conWatch.Cancel() |
668 | + cs.conWatch = nil |
669 | + } |
670 | } |
671 | |
672 | // start connects to the bus, gets the initial NetworkManager state, and sets |
673 | // up the watch. |
674 | -func (cs *connectedState) start() networkmanager.State { |
675 | +func (cs *ConnectedState) start() networkmanager.State { |
676 | var initial networkmanager.State |
677 | var stateCh <-chan networkmanager.State |
678 | var primary string |
679 | @@ -72,8 +107,9 @@ |
680 | cs.connAttempts += ar.Redial() |
681 | nm := networkmanager.New(cs.endp, cs.log) |
682 | |
683 | + cs.reset() |
684 | // set up the watch |
685 | - stateCh, err = nm.WatchState() |
686 | + stateCh, cs.stateWatch, err = nm.WatchState() |
687 | if err != nil { |
688 | cs.log.Debugf("failed to set up the state watch: %s", err) |
689 | goto Continue |
690 | @@ -87,15 +123,15 @@ |
691 | } |
692 | cs.log.Debugf("got initial state of %s", initial) |
693 | |
694 | + conCh, cs.conWatch, err = nm.WatchPrimaryConnection() |
695 | + if err != nil { |
696 | + cs.log.Debugf("failed to set up the connection watch: %s", err) |
697 | + goto Continue |
698 | + } |
699 | + |
700 | primary = nm.GetPrimaryConnection() |
701 | cs.log.Debugf("primary connection starts as %#v", primary) |
702 | |
703 | - conCh, err = nm.WatchPrimaryConnection() |
704 | - if err != nil { |
705 | - cs.log.Debugf("failed to set up the connection watch: %s", err) |
706 | - goto Continue |
707 | - } |
708 | - |
709 | cs.networkStateCh = stateCh |
710 | cs.networkConCh = conCh |
711 | |
712 | @@ -107,9 +143,11 @@ |
713 | } |
714 | } |
715 | |
716 | -// connectedStateStep takes one step forwards in the “am I connected?” |
717 | +var errCanceled = errors.New("canceled") |
718 | + |
719 | +// step takes one step forwards in the “am I connected?” |
720 | // answering state machine. |
721 | -func (cs *connectedState) connectedStateStep() (bool, error) { |
722 | +func (cs *ConnectedState) step() (bool, error) { |
723 | stabilizingTimeout := cs.config.StabilizingTimeout.Duration |
724 | recheckTimeout := cs.config.RecheckTimeout.Duration |
725 | log := cs.log |
726 | @@ -117,6 +155,8 @@ |
727 | Loop: |
728 | for { |
729 | select { |
730 | + case <-cs.done: |
731 | + return false, errCanceled |
732 | case <-cs.networkConCh: |
733 | cs.webgetCh = nil |
734 | cs.timer.Reset(stabilizingTimeout) |
735 | @@ -155,8 +195,13 @@ |
736 | case <-cs.timer.C: |
737 | if cs.currentState == networkmanager.ConnectedGlobal { |
738 | log.Debugf("connectivity: timer signal, state: ConnectedGlobal, checking...") |
739 | - cs.webgetCh = make(chan bool) |
740 | - go cs.webget(cs.webgetCh) |
741 | + // use a buffered channel, otherwise |
742 | + // we may leak webcheckers that cannot |
743 | + // send their result because we have |
744 | + // cleared webgetCh and wont receive |
745 | + // on it |
746 | + cs.webgetCh = make(chan bool, 1) |
747 | + go cs.webchk.Webcheck(cs.webgetCh) |
748 | } |
749 | |
750 | case connected := <-cs.webgetCh: |
751 | @@ -173,35 +218,49 @@ |
752 | return cs.lastSent, nil |
753 | } |
754 | |
755 | -// ConnectedState sends the initial NetworkManager state and changes to it |
756 | +// Track sends the initial NetworkManager state and changes to it |
757 | // over the "out" channel. Sends "false" as soon as it detects trouble, "true" |
758 | // after checking actual connectivity. |
759 | // |
760 | -// The endpoint need not be dialed; connectivity will Dial() and Close() |
761 | -// it as it sees fit. |
762 | -func ConnectedState(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger, out chan<- bool) { |
763 | - wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log) |
764 | - cs := &connectedState{ |
765 | - config: config, |
766 | - log: log, |
767 | - endp: endp, |
768 | - webget: wg.Webcheck, |
769 | - } |
770 | +func (cs *ConnectedState) Track(out chan<- bool) { |
771 | |
772 | Start: |
773 | - log.Debugf("sending initial 'disconnected'.") |
774 | - out <- false |
775 | + cs.log.Debugf("sending initial 'disconnected'.") |
776 | + select { |
777 | + case <-cs.done: |
778 | + return |
779 | + case out <- false: |
780 | + } |
781 | cs.lastSent = false |
782 | cs.currentState = cs.start() |
783 | + defer cs.reset() |
784 | cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration) |
785 | |
786 | for { |
787 | - v, err := cs.connectedStateStep() |
788 | + v, err := cs.step() |
789 | + if err == errCanceled { |
790 | + return |
791 | + } |
792 | if err != nil { |
793 | // tear it all down and start over |
794 | - log.Errorf("%s", err) |
795 | + cs.log.Errorf("%s", err) |
796 | goto Start |
797 | } |
798 | - out <- v |
799 | + select { |
800 | + case <-cs.done: |
801 | + return |
802 | + case out <- v: |
803 | + } |
804 | + } |
805 | +} |
806 | + |
807 | +// Cancel stops the ConnectedState machinary. |
808 | +func (cs *ConnectedState) Cancel() { |
809 | + cs.doneLck.Lock() |
810 | + defer cs.doneLck.Unlock() |
811 | + if !cs.canceled { |
812 | + cs.canceled = true |
813 | + close(cs.done) |
814 | + cs.webchk.Close() |
815 | } |
816 | } |
817 | |
818 | === modified file 'bus/connectivity/connectivity_test.go' |
819 | --- bus/connectivity/connectivity_test.go 2015-01-21 17:55:35 +0000 |
820 | +++ bus/connectivity/connectivity_test.go 2015-03-06 13:26:11 +0000 |
821 | @@ -1,5 +1,5 @@ |
822 | /* |
823 | - Copyright 2013-2014 Canonical Ltd. |
824 | + Copyright 2013-2015 Canonical Ltd. |
825 | |
826 | This program is free software: you can redistribute it and/or modify it |
827 | under the terms of the GNU General Public License version 3, as published |
828 | @@ -58,22 +58,37 @@ |
829 | s.log = helpers.NewTestLogger(c, "debug") |
830 | } |
831 | |
832 | +var ( |
833 | + helloCon = dbus.ObjectPath("hello") |
834 | + helloConProps = map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{helloCon}} |
835 | +) |
836 | + |
837 | /* |
838 | - tests for connectedState's Start() method |
839 | + tests for ConnectedState's Start() method |
840 | */ |
841 | |
842 | // when given a working config and bus, Start() will work |
843 | func (s *ConnSuite) TestStartWorks(c *C) { |
844 | - endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting)) |
845 | - cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
846 | + endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting), helloCon) |
847 | + cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
848 | + |
849 | + nopTicker := make(chan []interface{}) |
850 | + testingbus.SetWatchSource(endp, "StateChanged", nopTicker) |
851 | + testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker) |
852 | + defer close(nopTicker) |
853 | |
854 | c.Check(cs.start(), Equals, networkmanager.Connecting) |
855 | } |
856 | |
857 | // if the bus fails a couple of times, we're still OK |
858 | func (s *ConnSuite) TestStartRetriesConnect(c *C) { |
859 | - endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting)) |
860 | - cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
861 | + endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting), helloCon) |
862 | + cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
863 | + |
864 | + nopTicker := make(chan []interface{}) |
865 | + testingbus.SetWatchSource(endp, "StateChanged", nopTicker) |
866 | + testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker) |
867 | + defer close(nopTicker) |
868 | |
869 | c.Check(cs.start(), Equals, networkmanager.Connecting) |
870 | c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work |
871 | @@ -81,8 +96,13 @@ |
872 | |
873 | // when the calls to NetworkManager fails for a bit, we're still OK |
874 | func (s *ConnSuite) TestStartRetriesCall(c *C) { |
875 | - endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting)) |
876 | - cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
877 | + endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting), helloCon) |
878 | + cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
879 | + |
880 | + nopTicker := make(chan []interface{}) |
881 | + testingbus.SetWatchSource(endp, "StateChanged", nopTicker) |
882 | + testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker) |
883 | + defer close(nopTicker) |
884 | |
885 | c.Check(cs.start(), Equals, networkmanager.Connecting) |
886 | |
887 | @@ -91,11 +111,19 @@ |
888 | |
889 | // when some of the calls to NetworkManager fails for a bit, we're still OK |
890 | func (s *ConnSuite) TestStartRetriesCall2(c *C) { |
891 | - cond := condition.Chain(3, condition.Work(true), 1, condition.Work(false), |
892 | + cond := condition.Chain(1, condition.Work(true), 1, condition.Work(false), |
893 | 1, condition.Work(true)) |
894 | |
895 | - endp := testingbus.NewTestingEndpoint(condition.Work(true), cond, uint32(networkmanager.Connecting)) |
896 | - cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
897 | + endp := testingbus.NewTestingEndpoint(condition.Work(true), cond, |
898 | + uint32(networkmanager.Connecting), helloCon, |
899 | + uint32(networkmanager.Connecting), helloCon, |
900 | + ) |
901 | + cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
902 | + |
903 | + nopTicker := make(chan []interface{}) |
904 | + testingbus.SetWatchSource(endp, "StateChanged", nopTicker) |
905 | + testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker) |
906 | + defer close(nopTicker) |
907 | |
908 | c.Check(cs.start(), Equals, networkmanager.Connecting) |
909 | } |
910 | @@ -105,17 +133,25 @@ |
911 | // watch, we recover and try again. |
912 | func (s *ConnSuite) TestStartRetriesWatch(c *C) { |
913 | nmcond := condition.Chain( |
914 | - 1, condition.Work(true), // 1 call to nm works |
915 | + 2, condition.Work(true), // 2 call to nm works |
916 | 1, condition.Work(false), // 1 call to nm fails |
917 | 0, condition.Work(true)) // and everything works from there on |
918 | endp := testingbus.NewTestingEndpoint(condition.Work(true), nmcond, |
919 | uint32(networkmanager.Connecting), |
920 | - uint32(networkmanager.ConnectedGlobal)) |
921 | - cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
922 | + uint32(networkmanager.Connecting), |
923 | + helloCon, |
924 | + ) |
925 | + cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp} |
926 | + watchTicker := make(chan []interface{}, 1) |
927 | + nopTicker := make(chan []interface{}) |
928 | + testingbus.SetWatchSource(endp, "StateChanged", watchTicker) |
929 | + testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker) |
930 | + defer close(nopTicker) |
931 | + defer close(watchTicker) |
932 | |
933 | c.Check(cs.start(), Equals, networkmanager.Connecting) |
934 | c.Check(cs.connAttempts, Equals, uint32(2)) |
935 | - c.Check(<-cs.networkStateCh, Equals, networkmanager.Connecting) |
936 | + watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)} |
937 | c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal) |
938 | } |
939 | |
940 | @@ -144,7 +180,7 @@ |
941 | } |
942 | } |
943 | |
944 | -func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error { |
945 | +func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) { |
946 | if member == "StateChanged" { |
947 | // we count never having gotten the state as happening "after" now. |
948 | rep.lock.RLock() |
949 | @@ -157,7 +193,7 @@ |
950 | d() |
951 | }() |
952 | } |
953 | - return nil |
954 | + return nil, nil |
955 | } |
956 | |
957 | func (*racyEndpoint) Close() {} |
958 | @@ -186,7 +222,7 @@ |
959 | func (s *ConnSuite) TestStartAvoidsRace(c *C) { |
960 | for delta := time.Second; delta > 1; delta /= 2 { |
961 | rep := &racyEndpoint{delta: delta} |
962 | - cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: rep} |
963 | + cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: rep} |
964 | f := Commentf("when delta=%s", delta) |
965 | c.Assert(cs.start(), Equals, networkmanager.Connecting, f) |
966 | c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f) |
967 | @@ -194,9 +230,18 @@ |
968 | } |
969 | |
970 | /* |
971 | - tests for connectedStateStep() |
972 | + tests for step() |
973 | */ |
974 | |
975 | +type testWebchk func(ch chan<- bool) |
976 | + |
977 | +func (x testWebchk) Webcheck(ch chan<- bool) { |
978 | + x(ch) |
979 | +} |
980 | + |
981 | +func (x testWebchk) Close() { |
982 | +} |
983 | + |
984 | func (s *ConnSuite) TestSteps(c *C) { |
985 | var webget_p condition.Interface = condition.Work(true) |
986 | recheck_timeout := 50 * time.Millisecond |
987 | @@ -205,24 +250,24 @@ |
988 | RecheckTimeout: config.ConfigTimeDuration{recheck_timeout}, |
989 | } |
990 | ch := make(chan networkmanager.State, 10) |
991 | - cs := &connectedState{ |
992 | + cs := &ConnectedState{ |
993 | config: cfg, |
994 | networkStateCh: ch, |
995 | timer: time.NewTimer(time.Second), |
996 | log: s.log, |
997 | - webget: func(ch chan<- bool) { ch <- webget_p.OK() }, |
998 | + webchk: testWebchk(func(ch chan<- bool) { ch <- webget_p.OK() }), |
999 | lastSent: false, |
1000 | } |
1001 | ch <- networkmanager.ConnectedGlobal |
1002 | - f, e := cs.connectedStateStep() |
1003 | + f, e := cs.step() |
1004 | c.Check(e, IsNil) |
1005 | c.Check(f, Equals, true) |
1006 | ch <- networkmanager.Disconnected |
1007 | ch <- networkmanager.ConnectedGlobal |
1008 | - f, e = cs.connectedStateStep() |
1009 | + f, e = cs.step() |
1010 | c.Check(e, IsNil) |
1011 | c.Check(f, Equals, false) |
1012 | - f, e = cs.connectedStateStep() |
1013 | + f, e = cs.step() |
1014 | c.Check(e, IsNil) |
1015 | c.Check(f, Equals, true) |
1016 | |
1017 | @@ -230,7 +275,7 @@ |
1018 | webget_p = condition.Fail2Work(1) |
1019 | ch <- networkmanager.Disconnected |
1020 | ch <- networkmanager.ConnectedGlobal |
1021 | - f, e = cs.connectedStateStep() |
1022 | + f, e = cs.step() |
1023 | c.Check(e, IsNil) |
1024 | c.Check(f, Equals, false) // first false is from the Disconnected |
1025 | |
1026 | @@ -239,7 +284,7 @@ |
1027 | _t := time.NewTimer(recheck_timeout / 2) |
1028 | |
1029 | go func() { |
1030 | - f, e := cs.connectedStateStep() |
1031 | + f, e := cs.step() |
1032 | c.Check(e, IsNil) |
1033 | _ch <- f |
1034 | }() |
1035 | @@ -257,15 +302,15 @@ |
1036 | ch <- networkmanager.Disconnected // this should not |
1037 | ch <- networkmanager.ConnectedGlobal // this should trigger a 'true' |
1038 | |
1039 | - f, e = cs.connectedStateStep() |
1040 | + f, e = cs.step() |
1041 | c.Check(e, IsNil) |
1042 | c.Check(f, Equals, false) |
1043 | - f, e = cs.connectedStateStep() |
1044 | + f, e = cs.step() |
1045 | c.Check(e, IsNil) |
1046 | c.Check(f, Equals, true) |
1047 | |
1048 | close(ch) // this should make it error out |
1049 | - _, e = cs.connectedStateStep() |
1050 | + _, e = cs.step() |
1051 | c.Check(e, NotNil) |
1052 | } |
1053 | |
1054 | @@ -285,32 +330,50 @@ |
1055 | } |
1056 | |
1057 | endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), |
1058 | - uint32(networkmanager.ConnectedGlobal), |
1059 | - uint32(networkmanager.Disconnected), |
1060 | + uint32(networkmanager.Disconnected), |
1061 | + helloCon, |
1062 | + uint32(networkmanager.Disconnected), |
1063 | + helloCon, |
1064 | ) |
1065 | |
1066 | - watchTicker := make(chan bool) |
1067 | - testingbus.SetWatchTicker(endp, watchTicker) |
1068 | + watchTicker := make(chan []interface{}) |
1069 | + testingbus.SetWatchSource(endp, "StateChanged", watchTicker) |
1070 | + nopTicker := make(chan []interface{}) |
1071 | + testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker) |
1072 | |
1073 | out := make(chan bool) |
1074 | dt := time.Second / 10 |
1075 | timer := time.NewTimer(dt) |
1076 | - go ConnectedState(endp, cfg, s.log, out) |
1077 | + cs := New(endp, cfg, s.log) |
1078 | + defer cs.Cancel() |
1079 | + go cs.Track(out) |
1080 | var v bool |
1081 | expecteds := []struct { |
1082 | - p bool |
1083 | - s string |
1084 | - n int |
1085 | + p bool |
1086 | + s string |
1087 | + todo string |
1088 | }{ |
1089 | - {false, "first state is always false", 0}, |
1090 | - {true, "then it should be true as per ConnectedGlobal above", 0}, |
1091 | - {false, "then it should be false (Disconnected)", 2}, |
1092 | - {false, "then it should be false again because it's restarted", 2}, |
1093 | + {false, "first state is always false", ""}, |
1094 | + {true, "then it should be true as per ConnectedGlobal above", "ConnectedGlobal"}, |
1095 | + {false, "then it should be false (Disconnected)", "Disconnected"}, |
1096 | + {false, "then it should be false again because it's restarted", "close"}, |
1097 | } |
1098 | |
1099 | + defer func() { |
1100 | + if watchTicker != nil { |
1101 | + close(watchTicker) |
1102 | + } |
1103 | + }() |
1104 | + defer close(nopTicker) |
1105 | for i, expected := range expecteds { |
1106 | - for j := 0; j < expected.n; j++ { |
1107 | - watchTicker <- true |
1108 | + switch expected.todo { |
1109 | + case "ConnectedGlobal": |
1110 | + watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)} |
1111 | + case "Disconnected": |
1112 | + watchTicker <- []interface{}{uint32(networkmanager.Disconnected)} |
1113 | + case "close": |
1114 | + close(watchTicker) |
1115 | + watchTicker = nil |
1116 | } |
1117 | timer.Reset(dt) |
1118 | select { |
1119 | @@ -335,31 +398,41 @@ |
1120 | |
1121 | endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), |
1122 | uint32(networkmanager.ConnectedGlobal), |
1123 | - map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("hello")}}, |
1124 | + helloCon, |
1125 | ) |
1126 | |
1127 | - watchTicker := make(chan bool) |
1128 | - testingbus.SetWatchTicker(endp, watchTicker) |
1129 | + watchTicker := make(chan []interface{}) |
1130 | + testingbus.SetWatchSource(endp, "PropertiesChanged", watchTicker) |
1131 | + nopTicker := make(chan []interface{}) |
1132 | + testingbus.SetWatchSource(endp, "StateChanged", nopTicker) |
1133 | |
1134 | out := make(chan bool) |
1135 | dt := time.Second / 10 |
1136 | timer := time.NewTimer(dt) |
1137 | - go ConnectedState(endp, cfg, s.log, out) |
1138 | + cs := New(endp, cfg, s.log) |
1139 | + defer cs.Cancel() |
1140 | + go cs.Track(out) |
1141 | var v bool |
1142 | expecteds := []struct { |
1143 | - p bool |
1144 | - s string |
1145 | - n int |
1146 | + p bool |
1147 | + s string |
1148 | + changedConn bool |
1149 | }{ |
1150 | - {false, "first state is always false", 0}, |
1151 | - {true, "then it should be true as per ConnectedGlobal above", 0}, |
1152 | - {false, "then, false (PrimaryConnection changed)", 2}, |
1153 | - {true, "then it should be true (webcheck passed)", 0}, |
1154 | + {false, "first state is always false", false}, |
1155 | + {true, "then it should be true as per ConnectedGlobal above", false}, |
1156 | + {false, "then, false (PrimaryConnection changed)", true}, |
1157 | + {true, "then it should be true (webcheck passed)", false}, |
1158 | } |
1159 | |
1160 | + defer func() { |
1161 | + if watchTicker != nil { |
1162 | + close(watchTicker) |
1163 | + } |
1164 | + }() |
1165 | + defer close(nopTicker) |
1166 | for i, expected := range expecteds { |
1167 | - for j := 0; j < expected.n; j++ { |
1168 | - watchTicker <- true |
1169 | + if expected.changedConn { |
1170 | + watchTicker <- []interface{}{helloConProps} |
1171 | } |
1172 | timer.Reset(dt) |
1173 | select { |
1174 | |
1175 | === modified file 'bus/connectivity/webchecker.go' |
1176 | --- bus/connectivity/webchecker.go 2015-02-05 13:38:31 +0000 |
1177 | +++ bus/connectivity/webchecker.go 2015-03-06 13:26:11 +0000 |
1178 | @@ -1,5 +1,5 @@ |
1179 | /* |
1180 | - Copyright 2013-2014 Canonical Ltd. |
1181 | + Copyright 2013-2015 Canonical Ltd. |
1182 | |
1183 | This program is free software: you can redistribute it and/or modify it |
1184 | under the terms of the GNU General Public License version 3, as published |
1185 | @@ -40,6 +40,8 @@ |
1186 | // contents match the target. If so, then it sends true; if anything |
1187 | // fails, it sends false. |
1188 | Webcheck(chan<- bool) |
1189 | + // Close idle connections. |
1190 | + Close() |
1191 | } |
1192 | |
1193 | type webchecker struct { |
1194 | @@ -89,3 +91,7 @@ |
1195 | ch <- false |
1196 | } |
1197 | } |
1198 | + |
1199 | +func (wb *webchecker) Close() { |
1200 | + wb.cli.Transport.(*http13.Transport).CloseIdleConnections() |
1201 | +} |
1202 | |
1203 | === modified file 'bus/connectivity/webchecker_test.go' |
1204 | --- bus/connectivity/webchecker_test.go 2015-02-05 13:38:31 +0000 |
1205 | +++ bus/connectivity/webchecker_test.go 2015-03-06 13:26:11 +0000 |
1206 | @@ -1,5 +1,5 @@ |
1207 | /* |
1208 | - Copyright 2013-2014 Canonical Ltd. |
1209 | + Copyright 2013-2015 Canonical Ltd. |
1210 | |
1211 | This program is free software: you can redistribute it and/or modify it |
1212 | under the terms of the GNU General Public License version 3, as published |
1213 | @@ -81,6 +81,7 @@ |
1214 | defer ts.Close() |
1215 | |
1216 | ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log) |
1217 | + defer ck.Close() |
1218 | ch := make(chan bool, 1) |
1219 | ck.Webcheck(ch) |
1220 | c.Check(<-ch, Equals, true) |
1221 | @@ -89,6 +90,7 @@ |
1222 | // Webchecker sends false if the download fails. |
1223 | func (s *WebcheckerSuite) TestActualFails(c *C) { |
1224 | ck := NewWebchecker("garbage://", "", 5*time.Second, s.log) |
1225 | + defer ck.Close() |
1226 | ch := make(chan bool, 1) |
1227 | ck.Webcheck(ch) |
1228 | c.Check(<-ch, Equals, false) |
1229 | @@ -100,6 +102,7 @@ |
1230 | defer ts.Close() |
1231 | |
1232 | ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log) |
1233 | + defer ck.Close() |
1234 | ch := make(chan bool, 1) |
1235 | ck.Webcheck(ch) |
1236 | c.Check(<-ch, Equals, false) |
1237 | @@ -112,6 +115,7 @@ |
1238 | defer ts.Close() |
1239 | |
1240 | ck := NewWebchecker(ts.URL, bigHash, 5*time.Second, s.log) |
1241 | + defer ck.Close() |
1242 | ch := make(chan bool, 1) |
1243 | ck.Webcheck(ch) |
1244 | c.Check(<-ch, Equals, false) |
1245 | @@ -131,6 +135,7 @@ |
1246 | }() |
1247 | |
1248 | ck := NewWebchecker(ts.URL, bigHash, time.Second, s.log) |
1249 | + defer ck.Close() |
1250 | ch := make(chan bool, 1) |
1251 | ck.Webcheck(ch) |
1252 | c.Check(<-ch, Equals, false) |
1253 | |
1254 | === modified file 'bus/endpoint.go' |
1255 | --- bus/endpoint.go 2015-01-22 09:52:07 +0000 |
1256 | +++ bus/endpoint.go 2015-03-06 13:26:11 +0000 |
1257 | @@ -35,10 +35,15 @@ |
1258 | type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error) |
1259 | type DispatchMap map[string]BusMethod |
1260 | |
1261 | +// Cancellable can be canceled. |
1262 | +type Cancellable interface { |
1263 | + Cancel() error |
1264 | +} |
1265 | + |
1266 | // bus.Endpoint represents the DBus connection itself. |
1267 | type Endpoint interface { |
1268 | GrabName(allowReplacement bool) <-chan error |
1269 | - WatchSignal(member string, f func(...interface{}), d func()) error |
1270 | + WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error) |
1271 | WatchMethod(DispatchMap, string, ...interface{}) |
1272 | Signal(string, string, []interface{}) error |
1273 | Call(member string, args []interface{}, rvs ...interface{}) error |
1274 | @@ -123,16 +128,16 @@ |
1275 | // sends the values over a channel, and d() would close the channel. |
1276 | // |
1277 | // XXX: untested |
1278 | -func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) error { |
1279 | +func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error) { |
1280 | watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member) |
1281 | if err != nil { |
1282 | endp.log.Debugf("failed to set up the watch: %s", err) |
1283 | - return err |
1284 | + return nil, err |
1285 | } |
1286 | |
1287 | go endp.unpackMessages(watch, f, d, member) |
1288 | |
1289 | - return nil |
1290 | + return watch, nil |
1291 | } |
1292 | |
1293 | // Call() invokes the provided member method (on the name, path and |
1294 | @@ -324,6 +329,6 @@ |
1295 | } |
1296 | f(endp.unpackOneMsg(msg, member)...) |
1297 | } |
1298 | - endp.log.Errorf("got not-OK from %s watch", member) |
1299 | + endp.log.Debugf("got not-OK from %s watch", member) |
1300 | d() |
1301 | } |
1302 | |
1303 | === modified file 'bus/haptic/haptic.go' |
1304 | --- bus/haptic/haptic.go 2014-08-08 01:07:38 +0000 |
1305 | +++ bus/haptic/haptic.go 2015-03-06 13:26:11 +0000 |
1306 | @@ -20,6 +20,7 @@ |
1307 | |
1308 | import ( |
1309 | "launchpad.net/ubuntu-push/bus" |
1310 | + "launchpad.net/ubuntu-push/bus/accounts" |
1311 | "launchpad.net/ubuntu-push/click" |
1312 | "launchpad.net/ubuntu-push/launch_helper" |
1313 | "launchpad.net/ubuntu-push/logger" |
1314 | @@ -36,12 +37,13 @@ |
1315 | type Haptic struct { |
1316 | bus bus.Endpoint |
1317 | log logger.Logger |
1318 | + acc accounts.Accounts |
1319 | fallback *launch_helper.Vibration |
1320 | } |
1321 | |
1322 | // New returns a new Haptic that'll use the provided bus.Endpoint |
1323 | -func New(endp bus.Endpoint, log logger.Logger, fallback *launch_helper.Vibration) *Haptic { |
1324 | - return &Haptic{endp, log, fallback} |
1325 | +func New(endp bus.Endpoint, log logger.Logger, acc accounts.Accounts, fallback *launch_helper.Vibration) *Haptic { |
1326 | + return &Haptic{endp, log, acc, fallback} |
1327 | } |
1328 | |
1329 | // Present presents the notification via a vibrate pattern |
1330 | @@ -50,6 +52,11 @@ |
1331 | panic("please check notification is not nil before calling present") |
1332 | } |
1333 | |
1334 | + if !haptic.acc.Vibrate() { |
1335 | + haptic.log.Debugf("[%s] vibrate disabled by user.", nid) |
1336 | + return false |
1337 | + } |
1338 | + |
1339 | vib := notification.Vibration(haptic.fallback) |
1340 | if vib == nil { |
1341 | haptic.log.Debugf("[%s] notification has no Vibrate.", nid) |
1342 | |
1343 | === modified file 'bus/haptic/haptic_test.go' |
1344 | --- bus/haptic/haptic_test.go 2014-08-08 01:07:38 +0000 |
1345 | +++ bus/haptic/haptic_test.go 2015-03-06 13:26:11 +0000 |
1346 | @@ -35,20 +35,36 @@ |
1347 | type hapticSuite struct { |
1348 | log *helpers.TestLogger |
1349 | app *click.AppId |
1350 | -} |
1351 | + acc *mockAccounts |
1352 | +} |
1353 | + |
1354 | +type mockAccounts struct { |
1355 | + vib bool |
1356 | + sil bool |
1357 | + snd string |
1358 | + err error |
1359 | +} |
1360 | + |
1361 | +func (m *mockAccounts) Start() error { return m.err } |
1362 | +func (m *mockAccounts) Cancel() error { return m.err } |
1363 | +func (m *mockAccounts) SilentMode() bool { return m.sil } |
1364 | +func (m *mockAccounts) Vibrate() bool { return m.vib } |
1365 | +func (m *mockAccounts) MessageSoundFile() string { return m.snd } |
1366 | +func (m *mockAccounts) String() string { return "<mockAccounts>" } |
1367 | |
1368 | var _ = Suite(&hapticSuite{}) |
1369 | |
1370 | func (hs *hapticSuite) SetUpTest(c *C) { |
1371 | hs.log = helpers.NewTestLogger(c, "debug") |
1372 | hs.app = clickhelp.MustParseAppId("com.example.test_test-app_0") |
1373 | + hs.acc = &mockAccounts{true, false, "xyzzy", nil} |
1374 | } |
1375 | |
1376 | // checks that Present() actually calls VibratePattern |
1377 | func (hs *hapticSuite) TestPresentPresents(c *C) { |
1378 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
1379 | |
1380 | - ec := New(endp, hs.log, nil) |
1381 | + ec := New(endp, hs.log, hs.acc, nil) |
1382 | notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100], "repeat": 2}`)} |
1383 | c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
1384 | callArgs := testibus.GetCallArgs(endp) |
1385 | @@ -61,7 +77,7 @@ |
1386 | func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) { |
1387 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
1388 | |
1389 | - ec := New(endp, hs.log, nil) |
1390 | + ec := New(endp, hs.log, hs.acc, nil) |
1391 | // note: no Repeat: |
1392 | notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100]}`)} |
1393 | c.Check(ec.Present(hs.app, "nid", ¬if), Equals, true) |
1394 | @@ -76,7 +92,7 @@ |
1395 | func (hs *hapticSuite) TestSkipIfMissing(c *C) { |
1396 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
1397 | |
1398 | - ec := New(endp, hs.log, nil) |
1399 | + ec := New(endp, hs.log, hs.acc, nil) |
1400 | // no Vibration in the notificaton |
1401 | c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false) |
1402 | // empty Vibration |
1403 | @@ -85,11 +101,24 @@ |
1404 | c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: json.RawMessage(`{}`)}), Equals, false) |
1405 | } |
1406 | |
1407 | +// check that Present() does not present if the accounts' Vibrate() returns false |
1408 | +func (hs *hapticSuite) TestPresentSkipsIfVibrateDisabled(c *C) { |
1409 | + endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
1410 | + fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2} |
1411 | + |
1412 | + ec := New(endp, hs.log, hs.acc, fallback) |
1413 | + notif := launch_helper.Notification{RawVibration: json.RawMessage(`true`)} |
1414 | + c.Assert(ec.Present(hs.app, "nid", ¬if), Equals, true) |
1415 | + // ok! |
1416 | + hs.acc.vib = false |
1417 | + c.Check(ec.Present(hs.app, "nid", ¬if), Equals, false) |
1418 | +} |
1419 | + |
1420 | // check that Present() panics if the notification is nil |
1421 | func (hs *hapticSuite) TestPanicsIfNil(c *C) { |
1422 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
1423 | |
1424 | - ec := New(endp, hs.log, nil) |
1425 | + ec := New(endp, hs.log, hs.acc, nil) |
1426 | // no notification at all |
1427 | c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`) |
1428 | } |
1429 | @@ -99,7 +128,7 @@ |
1430 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true)) |
1431 | fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2} |
1432 | |
1433 | - ec := New(endp, hs.log, fallback) |
1434 | + ec := New(endp, hs.log, hs.acc, fallback) |
1435 | notif := launch_helper.Notification{RawVibration: json.RawMessage(`false`)} |
1436 | c.Check(ec.Present(hs.app, "nid", ¬if), Equals, false) |
1437 | notif = launch_helper.Notification{RawVibration: json.RawMessage(`true`)} |
1438 | |
1439 | === modified file 'bus/networkmanager/networkmanager.go' |
1440 | --- bus/networkmanager/networkmanager.go 2015-01-22 09:52:07 +0000 |
1441 | +++ bus/networkmanager/networkmanager.go 2015-03-06 13:26:11 +0000 |
1442 | @@ -42,13 +42,13 @@ |
1443 | GetState() State |
1444 | // WatchState listens for changes to NetworkManager's state, and sends |
1445 | // them out over the channel returned. |
1446 | - WatchState() (<-chan State, error) |
1447 | + WatchState() (<-chan State, bus.Cancellable, error) |
1448 | // GetPrimaryConnection fetches and returns NetworkManager's current |
1449 | // primary connection. |
1450 | GetPrimaryConnection() string |
1451 | // WatchPrimaryConnection listens for changes of NetworkManager's |
1452 | // Primary Connection, and sends it out over the channel returned. |
1453 | - WatchPrimaryConnection() (<-chan string, error) |
1454 | + WatchPrimaryConnection() (<-chan string, bus.Cancellable, error) |
1455 | } |
1456 | |
1457 | type networkManager struct { |
1458 | @@ -85,9 +85,9 @@ |
1459 | return State(v) |
1460 | } |
1461 | |
1462 | -func (nm *networkManager) WatchState() (<-chan State, error) { |
1463 | +func (nm *networkManager) WatchState() (<-chan State, bus.Cancellable, error) { |
1464 | ch := make(chan State) |
1465 | - err := nm.bus.WatchSignal("StateChanged", |
1466 | + w, err := nm.bus.WatchSignal("StateChanged", |
1467 | func(ns ...interface{}) { |
1468 | stint, ok := ns[0].(uint32) |
1469 | if !ok { |
1470 | @@ -101,10 +101,10 @@ |
1471 | func() { close(ch) }) |
1472 | if err != nil { |
1473 | nm.log.Debugf("Failed to set up the watch: %s", err) |
1474 | - return nil, err |
1475 | + return nil, nil, err |
1476 | } |
1477 | |
1478 | - return ch, nil |
1479 | + return ch, w, nil |
1480 | } |
1481 | |
1482 | func (nm *networkManager) GetPrimaryConnection() string { |
1483 | @@ -124,9 +124,9 @@ |
1484 | return string(v) |
1485 | } |
1486 | |
1487 | -func (nm *networkManager) WatchPrimaryConnection() (<-chan string, error) { |
1488 | +func (nm *networkManager) WatchPrimaryConnection() (<-chan string, bus.Cancellable, error) { |
1489 | ch := make(chan string) |
1490 | - err := nm.bus.WatchSignal("PropertiesChanged", |
1491 | + w, err := nm.bus.WatchSignal("PropertiesChanged", |
1492 | func(ppsi ...interface{}) { |
1493 | pps, ok := ppsi[0].(map[string]dbus.Variant) |
1494 | if !ok { |
1495 | @@ -147,8 +147,8 @@ |
1496 | }, func() { close(ch) }) |
1497 | if err != nil { |
1498 | nm.log.Debugf("failed to set up the watch: %s", err) |
1499 | - return nil, err |
1500 | + return nil, nil, err |
1501 | } |
1502 | |
1503 | - return ch, nil |
1504 | + return ch, w, nil |
1505 | } |
1506 | |
1507 | === modified file 'bus/networkmanager/networkmanager_test.go' |
1508 | --- bus/networkmanager/networkmanager_test.go 2014-04-04 12:01:42 +0000 |
1509 | +++ bus/networkmanager/networkmanager_test.go 2015-03-06 13:26:11 +0000 |
1510 | @@ -90,8 +90,9 @@ |
1511 | func (s *NMSuite) TestWatchState(c *C) { |
1512 | tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal)) |
1513 | nm := New(tc, s.log) |
1514 | - ch, err := nm.WatchState() |
1515 | - c.Check(err, IsNil) |
1516 | + ch, w, err := nm.WatchState() |
1517 | + c.Assert(err, IsNil) |
1518 | + defer w.Cancel() |
1519 | l := []State{<-ch, <-ch, <-ch} |
1520 | c.Check(l, DeepEquals, []State{Unknown, Asleep, ConnectedGlobal}) |
1521 | } |
1522 | @@ -99,7 +100,7 @@ |
1523 | // WatchState returns on error if the dbus call fails |
1524 | func (s *NMSuite) TestWatchStateFails(c *C) { |
1525 | nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log) |
1526 | - _, err := nm.WatchState() |
1527 | + _, _, err := nm.WatchState() |
1528 | c.Check(err, NotNil) |
1529 | } |
1530 | |
1531 | @@ -107,8 +108,9 @@ |
1532 | func (s *NMSuite) TestWatchStateClosesOnWatchBail(c *C) { |
1533 | tc := testingbus.NewTestingEndpoint(nil, condition.Work(true)) |
1534 | nm := New(tc, s.log) |
1535 | - ch, err := nm.WatchState() |
1536 | - c.Check(err, IsNil) |
1537 | + ch, w, err := nm.WatchState() |
1538 | + c.Assert(err, IsNil) |
1539 | + defer w.Cancel() |
1540 | _, ok := <-ch |
1541 | c.Check(ok, Equals, false) |
1542 | } |
1543 | @@ -117,8 +119,9 @@ |
1544 | func (s *NMSuite) TestWatchStateSurvivesRubbishValues(c *C) { |
1545 | tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a") |
1546 | nm := New(tc, s.log) |
1547 | - ch, err := nm.WatchState() |
1548 | - c.Check(err, IsNil) |
1549 | + ch, w, err := nm.WatchState() |
1550 | + c.Assert(err, IsNil) |
1551 | + defer w.Cancel() |
1552 | _, ok := <-ch |
1553 | c.Check(ok, Equals, false) |
1554 | } |
1555 | @@ -164,8 +167,9 @@ |
1556 | mkPriConMap("/b/2"), |
1557 | mkPriConMap("/c/3")) |
1558 | nm := New(tc, s.log) |
1559 | - ch, err := nm.WatchPrimaryConnection() |
1560 | - c.Check(err, IsNil) |
1561 | + ch, w, err := nm.WatchPrimaryConnection() |
1562 | + c.Assert(err, IsNil) |
1563 | + defer w.Cancel() |
1564 | l := []string{<-ch, <-ch, <-ch} |
1565 | c.Check(l, DeepEquals, []string{"/a/1", "/b/2", "/c/3"}) |
1566 | } |
1567 | @@ -173,7 +177,7 @@ |
1568 | // WatchPrimaryConnection returns on error if the dbus call fails |
1569 | func (s *NMSuite) TestWatchPrimaryConnectionFails(c *C) { |
1570 | nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log) |
1571 | - _, err := nm.WatchPrimaryConnection() |
1572 | + _, _, err := nm.WatchPrimaryConnection() |
1573 | c.Check(err, NotNil) |
1574 | } |
1575 | |
1576 | @@ -181,8 +185,9 @@ |
1577 | func (s *NMSuite) TestWatchPrimaryConnectionClosesOnWatchBail(c *C) { |
1578 | tc := testingbus.NewTestingEndpoint(nil, condition.Work(true)) |
1579 | nm := New(tc, s.log) |
1580 | - ch, err := nm.WatchPrimaryConnection() |
1581 | - c.Check(err, IsNil) |
1582 | + ch, w, err := nm.WatchPrimaryConnection() |
1583 | + c.Assert(err, IsNil) |
1584 | + defer w.Cancel() |
1585 | _, ok := <-ch |
1586 | c.Check(ok, Equals, false) |
1587 | } |
1588 | @@ -191,8 +196,9 @@ |
1589 | func (s *NMSuite) TestWatchPrimaryConnectionSurvivesRubbishValues(c *C) { |
1590 | tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a") |
1591 | nm := New(tc, s.log) |
1592 | - ch, err := nm.WatchPrimaryConnection() |
1593 | + ch, w, err := nm.WatchPrimaryConnection() |
1594 | c.Assert(err, IsNil) |
1595 | + defer w.Cancel() |
1596 | _, ok := <-ch |
1597 | c.Check(ok, Equals, false) |
1598 | } |
1599 | @@ -204,8 +210,9 @@ |
1600 | map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}}, |
1601 | ) |
1602 | nm := New(tc, s.log) |
1603 | - ch, err := nm.WatchPrimaryConnection() |
1604 | + ch, w, err := nm.WatchPrimaryConnection() |
1605 | c.Assert(err, IsNil) |
1606 | + defer w.Cancel() |
1607 | v, ok := <-ch |
1608 | c.Check(ok, Equals, true) |
1609 | c.Check(v, Equals, "42") |
1610 | @@ -218,8 +225,9 @@ |
1611 | map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}}, |
1612 | ) |
1613 | nm := New(tc, s.log) |
1614 | - ch, err := nm.WatchPrimaryConnection() |
1615 | + ch, w, err := nm.WatchPrimaryConnection() |
1616 | c.Assert(err, IsNil) |
1617 | + defer w.Cancel() |
1618 | v, ok := <-ch |
1619 | c.Check(ok, Equals, true) |
1620 | c.Check(v, Equals, "42") |
1621 | |
1622 | === modified file 'bus/notifications/raw.go' |
1623 | --- bus/notifications/raw.go 2015-01-22 09:52:07 +0000 |
1624 | +++ bus/notifications/raw.go 2015-03-06 13:26:11 +0000 |
1625 | @@ -93,7 +93,7 @@ |
1626 | // and sends them over the channel provided |
1627 | func (raw *RawNotifications) WatchActions() (<-chan *RawAction, error) { |
1628 | ch := make(chan *RawAction) |
1629 | - err := raw.bus.WatchSignal("ActionInvoked", |
1630 | + _, err := raw.bus.WatchSignal("ActionInvoked", |
1631 | func(ns ...interface{}) { |
1632 | if len(ns) != 2 { |
1633 | raw.log.Debugf("ActionInvoked delivered %d things instead of 2", len(ns)) |
1634 | |
1635 | === modified file 'bus/notifications/raw_test.go' |
1636 | --- bus/notifications/raw_test.go 2014-08-15 10:33:04 +0000 |
1637 | +++ bus/notifications/raw_test.go 2015-03-06 13:26:11 +0000 |
1638 | @@ -111,14 +111,16 @@ |
1639 | errstr string |
1640 | endp bus.Endpoint |
1641 | works bool |
1642 | + src chan []interface{} |
1643 | } |
1644 | |
1645 | func (s *RawSuite) TestWatchActionsToleratesDBusWeirdness(c *C) { |
1646 | X := func(errstr string, args ...interface{}) tst { |
1647 | - endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), args) |
1648 | - // stop the endpoint from closing the channel: |
1649 | - testibus.SetWatchTicker(endp, make(chan bool)) |
1650 | - return tst{errstr, endp, errstr == ""} |
1651 | + endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true)) |
1652 | + src := make(chan []interface{}, 1) |
1653 | + testibus.SetWatchSource(endp, "ActionInvoked", src) |
1654 | + src <- args |
1655 | + return tst{errstr, endp, errstr == "", src} |
1656 | } |
1657 | |
1658 | ts := []tst{ |
1659 | @@ -146,6 +148,7 @@ |
1660 | } |
1661 | c.Check(s.log.Captured(), Matches, `(?ms).*`+t.errstr+`.*`) |
1662 | s.log.ResetCapture() |
1663 | + close(t.src) |
1664 | } |
1665 | |
1666 | } |
1667 | |
1668 | === modified file 'bus/testing/testing_endpoint.go' |
1669 | --- bus/testing/testing_endpoint.go 2015-01-29 09:48:40 +0000 |
1670 | +++ bus/testing/testing_endpoint.go 2015-03-06 13:26:11 +0000 |
1671 | @@ -36,13 +36,15 @@ |
1672 | } |
1673 | |
1674 | type testingEndpoint struct { |
1675 | - dialCond condition.Interface |
1676 | - callCond condition.Interface |
1677 | - retvals [][]interface{} |
1678 | - watchTicker chan bool |
1679 | - watchLck sync.RWMutex |
1680 | - callArgs []callArgs |
1681 | - callArgsLck sync.RWMutex |
1682 | + dialCond condition.Interface |
1683 | + callCond condition.Interface |
1684 | + usedLck sync.Mutex |
1685 | + used int |
1686 | + retvals [][]interface{} |
1687 | + watchSources map[string]chan []interface{} |
1688 | + watchLck sync.RWMutex |
1689 | + callArgs []callArgs |
1690 | + callArgsLck sync.RWMutex |
1691 | } |
1692 | |
1693 | // Build a bus.Endpoint that calls OK() on its condition before returning |
1694 | @@ -51,7 +53,7 @@ |
1695 | // NOTE: Call() always returns the first return value; Watch() will provide |
1696 | // each of them in turn, irrespective of whether Call has been called. |
1697 | func NewMultiValuedTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvalses ...[]interface{}) bus.Endpoint { |
1698 | - return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses} |
1699 | + return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})} |
1700 | } |
1701 | |
1702 | func NewTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvals ...interface{}) bus.Endpoint { |
1703 | @@ -59,15 +61,15 @@ |
1704 | for i, x := range retvals { |
1705 | retvalses[i] = []interface{}{x} |
1706 | } |
1707 | - return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses} |
1708 | + return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})} |
1709 | } |
1710 | |
1711 | -// If SetWatchTicker is called with a non-nil watchTicker, it is used |
1712 | -// instead of the default timeout to wait while sending values over |
1713 | -// WatchSignal. Set it to nil again to restore default behaviour. |
1714 | -func SetWatchTicker(tc bus.Endpoint, watchTicker chan bool) { |
1715 | +// If SetWatchSource is called with a non-nil watchSource, it is used |
1716 | +// instead of the default timeout and retvals to get values to send |
1717 | +// over WatchSignal. Set it to nil again to restore default behaviour. |
1718 | +func SetWatchSource(tc bus.Endpoint, member string, watchSource chan []interface{}) { |
1719 | tc.(*testingEndpoint).watchLck.Lock() |
1720 | - tc.(*testingEndpoint).watchTicker = watchTicker |
1721 | + tc.(*testingEndpoint).watchSources[member] = watchSource |
1722 | tc.(*testingEndpoint).watchLck.Unlock() |
1723 | } |
1724 | |
1725 | @@ -78,31 +80,77 @@ |
1726 | return tc.(*testingEndpoint).callArgs |
1727 | } |
1728 | |
1729 | +type watchCancel struct { |
1730 | + done chan struct{} |
1731 | + cancelled chan struct{} |
1732 | + lck sync.Mutex |
1733 | + member string |
1734 | +} |
1735 | + |
1736 | +// this waits for actual cancelllation for test convenience |
1737 | +func (wc *watchCancel) Cancel() error { |
1738 | + wc.lck.Lock() |
1739 | + defer wc.lck.Unlock() |
1740 | + if wc.cancelled != nil { |
1741 | + close(wc.cancelled) |
1742 | + wc.cancelled = nil |
1743 | + <-wc.done |
1744 | + } |
1745 | + return nil |
1746 | +} |
1747 | + |
1748 | // See Endpoint's WatchSignal. This WatchSignal will check its condition to |
1749 | // decide whether to return an error, or provide each of its return values |
1750 | -func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error { |
1751 | +// or values from the previously set watchSource for member. |
1752 | +func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) { |
1753 | if tc.callCond.OK() { |
1754 | + cancelled := make(chan struct{}) |
1755 | + done := make(chan struct{}) |
1756 | go func() { |
1757 | - for _, v := range tc.retvals { |
1758 | - f(v...) |
1759 | - tc.watchLck.RLock() |
1760 | - ticker := tc.watchTicker |
1761 | - tc.watchLck.RUnlock() |
1762 | - if ticker != nil { |
1763 | - _, ok := <-ticker |
1764 | + tc.watchLck.RLock() |
1765 | + source := tc.watchSources[member] |
1766 | + tc.watchLck.RUnlock() |
1767 | + if source == nil { |
1768 | + tc.usedLck.Lock() |
1769 | + idx := tc.used |
1770 | + tc.used++ |
1771 | + tc.usedLck.Unlock() |
1772 | + source = make(chan []interface{}) |
1773 | + go func() { |
1774 | + Feed: |
1775 | + for _, v := range tc.retvals[idx:] { |
1776 | + select { |
1777 | + case source <- v: |
1778 | + case <-cancelled: |
1779 | + break Feed |
1780 | + } |
1781 | + select { |
1782 | + case <-time.After(10 * time.Millisecond): |
1783 | + case <-cancelled: |
1784 | + break Feed |
1785 | + } |
1786 | + } |
1787 | + close(source) |
1788 | + }() |
1789 | + } |
1790 | + Receive: |
1791 | + for { |
1792 | + select { |
1793 | + case v, ok := <-source: |
1794 | if !ok { |
1795 | - // bail out |
1796 | - return |
1797 | + break Receive |
1798 | } |
1799 | - } else { |
1800 | - time.Sleep(10 * time.Millisecond) |
1801 | + f(v...) |
1802 | + case <-cancelled: |
1803 | + break Receive |
1804 | } |
1805 | } |
1806 | d() |
1807 | + close(done) |
1808 | }() |
1809 | - return nil |
1810 | + return &watchCancel{cancelled: cancelled, done: done, member: member}, nil |
1811 | } else { |
1812 | - return errors.New("no way") |
1813 | + return nil, errors.New("no way") |
1814 | } |
1815 | } |
1816 | |
1817 | @@ -116,20 +164,24 @@ |
1818 | if tc.callCond.OK() { |
1819 | expected := len(rvs) |
1820 | var provided int |
1821 | - if len(tc.retvals) == 0 { |
1822 | + tc.usedLck.Lock() |
1823 | + idx := tc.used |
1824 | + tc.used++ |
1825 | + tc.usedLck.Unlock() |
1826 | + if len(tc.retvals) <= idx { |
1827 | if expected != 0 { |
1828 | panic("No return values provided!") |
1829 | } |
1830 | provided = 0 |
1831 | } else { |
1832 | - provided = len(tc.retvals[0]) |
1833 | + provided = len(tc.retvals[idx]) |
1834 | } |
1835 | if provided != expected { |
1836 | return errors.New("provided/expected return vals mismatch") |
1837 | } |
1838 | if provided != 0 { |
1839 | x := dbus.NewMethodCallMessage("", "", "", "") |
1840 | - err := x.AppendArgs(tc.retvals[0]...) |
1841 | + err := x.AppendArgs(tc.retvals[idx]...) |
1842 | if err != nil { |
1843 | return err |
1844 | } |
1845 | |
1846 | === modified file 'bus/testing/testing_endpoint_test.go' |
1847 | --- bus/testing/testing_endpoint_test.go 2014-07-04 23:00:42 +0000 |
1848 | +++ bus/testing/testing_endpoint_test.go 2015-03-06 13:26:11 +0000 |
1849 | @@ -17,11 +17,13 @@ |
1850 | package testing |
1851 | |
1852 | import ( |
1853 | + "testing" |
1854 | + "time" |
1855 | + |
1856 | . "launchpad.net/gocheck" |
1857 | + |
1858 | "launchpad.net/ubuntu-push/bus" |
1859 | "launchpad.net/ubuntu-push/testing/condition" |
1860 | - "testing" |
1861 | - "time" |
1862 | ) |
1863 | |
1864 | // hook up gocheck |
1865 | @@ -100,8 +102,9 @@ |
1866 | var m, n uint32 = 42, 17 |
1867 | endp := NewTestingEndpoint(nil, condition.Work(true), m, n) |
1868 | ch := make(chan uint32) |
1869 | - e := endp.WatchSignal("what", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) }) |
1870 | - c.Check(e, IsNil) |
1871 | + w, e := endp.WatchSignal("which", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) }) |
1872 | + c.Assert(e, IsNil) |
1873 | + defer w.Cancel() |
1874 | c.Check(<-ch, Equals, m) |
1875 | c.Check(<-ch, Equals, n) |
1876 | } |
1877 | @@ -110,8 +113,9 @@ |
1878 | func (s *TestingEndpointSuite) TestWatchDestructor(c *C) { |
1879 | endp := NewTestingEndpoint(nil, condition.Work(true)) |
1880 | ch := make(chan uint32) |
1881 | - e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) }) |
1882 | - c.Check(e, IsNil) |
1883 | + w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) }) |
1884 | + c.Assert(e, IsNil) |
1885 | + defer w.Cancel() |
1886 | _, ok := <-ch |
1887 | c.Check(ok, Equals, false) |
1888 | } |
1889 | @@ -130,25 +134,28 @@ |
1890 | // Test that WatchSignal() with a negative condition returns an error. |
1891 | func (s *TestingEndpointSuite) TestWatchFails(c *C) { |
1892 | endp := NewTestingEndpoint(nil, condition.Work(false)) |
1893 | - e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {}) |
1894 | + w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {}) |
1895 | c.Check(e, NotNil) |
1896 | + c.Check(w, IsNil) |
1897 | } |
1898 | |
1899 | -// Test WatchSignal can use the WatchTicker instead of a timeout (if |
1900 | +// Test WatchSignal can use a watchSource instead of a timeout and retvals (if |
1901 | // the former is not nil) |
1902 | -func (s *TestingEndpointSuite) TestWatchTicker(c *C) { |
1903 | - watchTicker := make(chan bool, 3) |
1904 | - watchTicker <- true |
1905 | - watchTicker <- true |
1906 | - watchTicker <- true |
1907 | +func (s *TestingEndpointSuite) TestWatchSources(c *C) { |
1908 | + watchTicker := make(chan []interface{}, 3) |
1909 | + watchTicker <- []interface{}{true} |
1910 | + watchTicker <- []interface{}{true} |
1911 | + watchTicker <- []interface{}{true} |
1912 | c.Assert(len(watchTicker), Equals, 3) |
1913 | |
1914 | endp := NewTestingEndpoint(nil, condition.Work(true), 0, 0) |
1915 | - SetWatchTicker(endp, watchTicker) |
1916 | + SetWatchSource(endp, "what", watchTicker) |
1917 | ch := make(chan int) |
1918 | - e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) }) |
1919 | - c.Check(e, IsNil) |
1920 | + w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) }) |
1921 | + c.Assert(e, IsNil) |
1922 | + defer w.Cancel() |
1923 | |
1924 | + close(watchTicker) |
1925 | // wait for the destructor to be called |
1926 | select { |
1927 | case <-time.Tick(10 * time.Millisecond): |
1928 | @@ -156,8 +163,21 @@ |
1929 | case <-ch: |
1930 | } |
1931 | |
1932 | - // now if all went well, the ticker will have been tuck twice. |
1933 | - c.Assert(len(watchTicker), Equals, 1) |
1934 | + // now if all went well, the ticker will have been exhausted. |
1935 | + c.Assert(len(watchTicker), Equals, 0) |
1936 | +} |
1937 | + |
1938 | +// Test that WatchSignal() calls the destructor callback when canceled. |
1939 | +func (s *TestingEndpointSuite) TestWatchCancel(c *C) { |
1940 | + endp := NewTestingEndpoint(nil, condition.Work(true)) |
1941 | + ch := make(chan uint32) |
1942 | + w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) }) |
1943 | + c.Assert(e, IsNil) |
1944 | + defer w.Cancel() |
1945 | + SetWatchSource(endp, "what", make(chan []interface{})) |
1946 | + w.Cancel() |
1947 | + _, ok := <-ch |
1948 | + c.Check(ok, Equals, false) |
1949 | } |
1950 | |
1951 | // Tests that GetProperty() works |
1952 | |
1953 | === modified file 'client/client.go' |
1954 | --- client/client.go 2015-01-26 21:00:27 +0000 |
1955 | +++ client/client.go 2015-03-06 13:26:11 +0000 |
1956 | @@ -280,8 +280,10 @@ |
1957 | |
1958 | // takeTheBus starts the connection(s) to D-Bus and sets up associated event channels |
1959 | func (client *PushClient) takeTheBus() error { |
1960 | - go connectivity.ConnectedState(client.connectivityEndp, |
1961 | - client.config.ConnectivityConfig, client.log, client.connCh) |
1962 | + fmt.Println("FOO") |
1963 | + cs := connectivity.New(client.connectivityEndp, |
1964 | + client.config.ConnectivityConfig, client.log) |
1965 | + go cs.Track(client.connCh) |
1966 | util.NewAutoRedialer(client.systemImageEndp).Redial() |
1967 | sysimg := systemimage.New(client.systemImageEndp, client.log) |
1968 | info, err := sysimg.Info() |
1969 | |
1970 | === modified file 'client/client_test.go' |
1971 | --- client/client_test.go 2015-02-06 13:09:16 +0000 |
1972 | +++ client/client_test.go 2015-03-06 13:26:11 +0000 |
1973 | @@ -31,6 +31,7 @@ |
1974 | "testing" |
1975 | "time" |
1976 | |
1977 | + "launchpad.net/go-dbus/v1" |
1978 | . "launchpad.net/gocheck" |
1979 | |
1980 | "launchpad.net/ubuntu-push/accounts" |
1981 | @@ -205,12 +206,7 @@ |
1982 | } |
1983 | |
1984 | func (cs *clientSuite) TearDownTest(c *C) { |
1985 | - //fmt.Println("GOROUTINE# ", runtime.NumGoroutine()) |
1986 | - /* |
1987 | - var x [16*1024]byte |
1988 | - sz := runtime.Stack(x[:], true) |
1989 | - fmt.Println(string(x[:sz])) |
1990 | - */ |
1991 | + //helpers.DumpGoroutines() |
1992 | } |
1993 | |
1994 | type sqlientSuite struct{ clientSuite } |
1995 | @@ -657,13 +653,19 @@ |
1996 | // testing endpoints |
1997 | cCond := condition.Fail2Work(7) |
1998 | cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true), |
1999 | - uint32(networkmanager.ConnectedGlobal), |
2000 | + uint32(networkmanager.Connecting), |
2001 | + dbus.ObjectPath("hello"), |
2002 | + uint32(networkmanager.Connecting), |
2003 | + dbus.ObjectPath("hello"), |
2004 | ) |
2005 | siCond := condition.Fail2Work(2) |
2006 | siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}}) |
2007 | - tickerCh := make(chan bool) |
2008 | - testibus.SetWatchTicker(cEndp, tickerCh) |
2009 | + tickerCh := make(chan []interface{}) |
2010 | + nopTickerCh := make(chan []interface{}) |
2011 | + testibus.SetWatchSource(cEndp, "StateChanged", tickerCh) |
2012 | + testibus.SetWatchSource(cEndp, "PropertiesChanged", nopTickerCh) |
2013 | defer close(tickerCh) |
2014 | + defer close(nopTickerCh) |
2015 | // ok, create the thing |
2016 | cli := NewPushClient(cs.configPath, cs.leveldbPath) |
2017 | cli.log = cs.log |
2018 | @@ -679,6 +681,7 @@ |
2019 | c.Assert(cli.takeTheBus(), IsNil) |
2020 | |
2021 | c.Check(takeNextBool(cli.connCh), Equals, false) |
2022 | + tickerCh <- []interface{}{uint32(networkmanager.ConnectedGlobal)} |
2023 | c.Check(takeNextBool(cli.connCh), Equals, true) |
2024 | // the connectivity endpoint retried until connected |
2025 | c.Check(cCond.OK(), Equals, true) |
2026 | |
2027 | === modified file 'client/service/postal.go' |
2028 | --- client/service/postal.go 2015-01-21 17:21:42 +0000 |
2029 | +++ client/service/postal.go 2015-03-06 13:26:11 +0000 |
2030 | @@ -24,6 +24,7 @@ |
2031 | "code.google.com/p/go-uuid/uuid" |
2032 | |
2033 | "launchpad.net/ubuntu-push/bus" |
2034 | + "launchpad.net/ubuntu-push/bus/accounts" |
2035 | "launchpad.net/ubuntu-push/bus/emblemcounter" |
2036 | "launchpad.net/ubuntu-push/bus/haptic" |
2037 | "launchpad.net/ubuntu-push/bus/notifications" |
2038 | @@ -75,6 +76,7 @@ |
2039 | // the endpoints are only exposed for testing from client |
2040 | // XXX: uncouple some more so this isn't necessary |
2041 | EmblemCounterEndp bus.Endpoint |
2042 | + AccountsEndp bus.Endpoint |
2043 | HapticEndp bus.Endpoint |
2044 | NotificationsEndp bus.Endpoint |
2045 | UnityGreeterEndp bus.Endpoint |
2046 | @@ -82,6 +84,7 @@ |
2047 | // presenters: |
2048 | Presenters []Presenter |
2049 | emblemCounter *emblemcounter.EmblemCounter |
2050 | + accounts accounts.Accounts |
2051 | haptic *haptic.Haptic |
2052 | notifications *notifications.RawNotifications |
2053 | sound *sounds.Sound |
2054 | @@ -117,6 +120,7 @@ |
2055 | svc.fallbackSound = setup.FallbackSound |
2056 | svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log) |
2057 | svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log) |
2058 | + svc.AccountsEndp = bus.SystemBus.Endpoint(accounts.BusAddress, log) |
2059 | svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log) |
2060 | svc.UnityGreeterEndp = bus.SessionBus.Endpoint(unitygreeter.BusAddress, log) |
2061 | svc.WindowStackEndp = bus.SessionBus.Endpoint(windowstack.BusAddress, log) |
2062 | @@ -158,8 +162,13 @@ |
2063 | svc.urlDispatcher = urldispatcher.New(svc.Log) |
2064 | svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log) |
2065 | svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log) |
2066 | - svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.fallbackVibration) |
2067 | - svc.sound = sounds.New(svc.Log, svc.fallbackSound) |
2068 | + svc.accounts = accounts.New(svc.AccountsEndp, svc.Log) |
2069 | + err = svc.accounts.Start() |
2070 | + if err != nil { |
2071 | + return err |
2072 | + } |
2073 | + svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.accounts, svc.fallbackVibration) |
2074 | + svc.sound = sounds.New(svc.Log, svc.accounts, svc.fallbackSound) |
2075 | svc.messagingMenu = messaging.New(svc.Log) |
2076 | svc.Presenters = []Presenter{ |
2077 | svc.notifications, |
2078 | @@ -228,6 +237,7 @@ |
2079 | }{ |
2080 | {"notifications", svc.NotificationsEndp}, |
2081 | {"emblemcounter", svc.EmblemCounterEndp}, |
2082 | + {"accounts", svc.AccountsEndp}, |
2083 | {"haptic", svc.HapticEndp}, |
2084 | {"unitygreeter", svc.UnityGreeterEndp}, |
2085 | {"windowstack", svc.WindowStackEndp}, |
2086 | |
2087 | === modified file 'client/service/postal_test.go' |
2088 | --- client/service/postal_test.go 2014-09-09 22:54:04 +0000 |
2089 | +++ client/service/postal_test.go 2015-03-06 13:26:11 +0000 |
2090 | @@ -169,6 +169,8 @@ |
2091 | hapticBus bus.Endpoint |
2092 | unityGreeterBus bus.Endpoint |
2093 | winStackBus bus.Endpoint |
2094 | + accountsBus bus.Endpoint |
2095 | + accountsCh chan []interface{} |
2096 | fakeLauncher *fakeHelperLauncher |
2097 | getTempDir func(string) (string, error) |
2098 | oldIsBlisted func(*click.AppId) bool |
2099 | @@ -194,6 +196,7 @@ |
2100 | ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
2101 | ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
2102 | ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
2103 | + ps.accountsBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), map[string]dbus.Variant{"IncomingMessageVibrate": dbus.Variant{true}}) |
2104 | ps.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true)) |
2105 | ps.unityGreeterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), false) |
2106 | ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{}) |
2107 | @@ -206,11 +209,15 @@ |
2108 | tmpDir := filepath.Join(d, pkgName) |
2109 | return tmpDir, os.MkdirAll(tmpDir, 0700) |
2110 | } |
2111 | + |
2112 | + ps.accountsCh = make(chan []interface{}) |
2113 | + testibus.SetWatchSource(ps.accountsBus, "PropertiesChanged", ps.accountsCh) |
2114 | } |
2115 | |
2116 | func (ps *postalSuite) TearDownTest(c *C) { |
2117 | isBlacklisted = ps.oldIsBlisted |
2118 | launch_helper.GetTempDir = ps.getTempDir |
2119 | + close(ps.accountsCh) |
2120 | } |
2121 | |
2122 | func (ts *trivialPostalSuite) SetUpTest(c *C) { |
2123 | @@ -227,6 +234,7 @@ |
2124 | pst.Bus = ps.bus |
2125 | pst.NotificationsEndp = ps.notifBus |
2126 | pst.EmblemCounterEndp = ps.counterBus |
2127 | + pst.AccountsEndp = ps.accountsBus |
2128 | pst.HapticEndp = ps.hapticBus |
2129 | pst.UnityGreeterEndp = ps.unityGreeterBus |
2130 | pst.WindowStackEndp = ps.winStackBus |
2131 | @@ -544,6 +552,7 @@ |
2132 | svc := NewPostalService(ps.cfg, ps.log) |
2133 | svc.Bus = endp |
2134 | svc.EmblemCounterEndp = endp |
2135 | + svc.AccountsEndp = ps.accountsBus |
2136 | svc.HapticEndp = endp |
2137 | svc.NotificationsEndp = endp |
2138 | svc.UnityGreeterEndp = ps.unityGreeterBus |
2139 | @@ -552,6 +561,10 @@ |
2140 | svc.fallbackVibration = &launch_helper.Vibration{Pattern: []uint32{1}} |
2141 | c.Assert(svc.Start(), IsNil) |
2142 | |
2143 | + nopTicker := make(chan []interface{}) |
2144 | + testibus.SetWatchSource(endp, "ActionInvoked", nopTicker) |
2145 | + defer close(nopTicker) |
2146 | + |
2147 | // Persist is false so we just check the log |
2148 | card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false} |
2149 | vib := json.RawMessage(`true`) |
2150 | @@ -837,6 +850,10 @@ |
2151 | } |
2152 | |
2153 | func (ps *postalSuite) TestBlacklisted(c *C) { |
2154 | + ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{}, |
2155 | + []windowstack.WindowsInfo{}, |
2156 | + []windowstack.WindowsInfo{}, |
2157 | + []windowstack.WindowsInfo{}) |
2158 | svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log)) |
2159 | svc.Start() |
2160 | ps.blacklisted = false |
2161 | |
2162 | === modified file 'debian/changelog' |
2163 | --- debian/changelog 2015-02-09 11:08:02 +0000 |
2164 | +++ debian/changelog 2015-03-06 13:26:11 +0000 |
2165 | @@ -1,42 +1,54 @@ |
2166 | +ubuntu-push (0.68-0ubuntu1) UNRELEASED; urgency=medium |
2167 | + |
2168 | + [ John R. Lenton ] |
2169 | + * Use settings from org.freedesktop.Accounts for silent mode, when to |
2170 | + vibrate, and default sound. [client] (LP: #1426418, LP: #1427702) |
2171 | + |
2172 | + [ Samuele "Yak Shaver of the Year" Pedroni ] |
2173 | + * Closing webchecker (avoids leaving goroutines around in testing). [client] |
2174 | + * WatchSignal cancelling, and connectivity exposed cancelling, make |
2175 | + connectivity start not leave watches behind (more goroutine |
2176 | + cleanup). [client] |
2177 | + * TestTakeTheBusWorks doesn't block anymore, fixed leaking of |
2178 | + webcheckers. [client] |
2179 | + |
2180 | + -- John R. Lenton <john.lenton@canonical.com> Fri, 06 Mar 2015 10:50:34 +0000 |
2181 | + |
2182 | ubuntu-push (0.67+15.04.20150209-0ubuntu1) vivid; urgency=medium |
2183 | |
2184 | [ John R. Lenton ] |
2185 | * Updated precommit script. [dev] |
2186 | - |
2187 | * Include code examples in docs (instead of repeating). [docs] |
2188 | + * Cleanup and improve logging, and make log messages more |
2189 | + consistent. [client] |
2190 | + * Partially work around bug lp:1390663 in a minimally intrusive way |
2191 | + (real fix will have to wait). [client] |
2192 | + * Add an explicit check and log message for nil error on webcheck's |
2193 | + CopyN. [client] |
2194 | |
2195 | + [ Samuele Pedroni ] |
2196 | * Make tests more robust in the face of go 1.3 [client, server] |
2197 | - |
2198 | * Introduce StartClientAuthFlex for acceptance tests: Start a client |
2199 | with auth, take a devId regexp, don't check any client event; support |
2200 | connbroken in acceptanceclient. [server] |
2201 | - |
2202 | - * Cleanup and improve logging, and make log messages more |
2203 | - consistent. [client] |
2204 | - |
2205 | - * Partially work around bug lp:1390663 in a minimally intrusive way |
2206 | - (real fix will have to wait). [client] |
2207 | - |
2208 | + * Clean up goroutines in tests. [client] |
2209 | + * Workaround gc issue with 1.3 and 32 bits. Fixes FTBFS. [client] |
2210 | + |
2211 | + [ Bret Barker ] |
2212 | * Add SIGQUIT handler to spit out stack dumps; more logging |
2213 | tweaks. [client, server] |
2214 | + * Log line nums, enabled when logLevel = debug. [client server] |
2215 | |
2216 | + [ Roberto Alsina ] |
2217 | * Adds a couple of buttons to exercise more APIs, version bump to |
2218 | 0.44. [sample app] |
2219 | |
2220 | + [ Guillermo Gonzalez ] |
2221 | * Add APIError to server/acceptance/kit that includes the body for |
2222 | debugging. [server] |
2223 | - |
2224 | * Add DisableKeepAlives and MaxIdleConnsPerHost to the APIClient |
2225 | SetupClient method. [server] |
2226 | |
2227 | - * Clean up goroutines in tests. [client] |
2228 | - |
2229 | - * Add an explicit check and log message for nil error on webcheck's CopyN. [client] |
2230 | - |
2231 | - * Log line nums, enabled when logLevel = debug. [client server] |
2232 | - |
2233 | - * Workaround gc issue with 1.3 and 32 bits. Fixes FTBFS. [client] |
2234 | - |
2235 | -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Mon, 09 Feb 2015 11:08:02 +0000 |
2236 | |
2237 | ubuntu-push (0.66+15.04.20141211-0ubuntu1) vivid; urgency=medium |
2238 | |
2239 | === modified file 'sounds/sounds.go' |
2240 | --- sounds/sounds.go 2014-08-08 09:03:42 +0000 |
2241 | +++ sounds/sounds.go 2015-03-06 13:26:11 +0000 |
2242 | @@ -25,6 +25,7 @@ |
2243 | |
2244 | "launchpad.net/go-xdg/v0" |
2245 | |
2246 | + "launchpad.net/ubuntu-push/bus/accounts" |
2247 | "launchpad.net/ubuntu-push/click" |
2248 | "launchpad.net/ubuntu-push/launch_helper" |
2249 | "launchpad.net/ubuntu-push/logger" |
2250 | @@ -33,15 +34,17 @@ |
2251 | type Sound struct { |
2252 | player string |
2253 | log logger.Logger |
2254 | + acc accounts.Accounts |
2255 | fallback string |
2256 | dataDirs func() []string |
2257 | dataFind func(string) (string, error) |
2258 | } |
2259 | |
2260 | -func New(log logger.Logger, fallback string) *Sound { |
2261 | +func New(log logger.Logger, acc accounts.Accounts, fallback string) *Sound { |
2262 | return &Sound{ |
2263 | player: "paplay", |
2264 | log: log, |
2265 | + acc: acc, |
2266 | fallback: fallback, |
2267 | dataDirs: xdg.Data.Dirs, |
2268 | dataFind: xdg.Data.Find, |
2269 | @@ -53,7 +56,17 @@ |
2270 | panic("please check notification is not nil before calling present") |
2271 | } |
2272 | |
2273 | - sound := notification.Sound(snd.fallback) |
2274 | + if snd.acc.SilentMode() { |
2275 | + snd.log.Debugf("[%s] no sounds: silent mode on.", nid) |
2276 | + return false |
2277 | + } |
2278 | + |
2279 | + fallback := snd.acc.MessageSoundFile() |
2280 | + if fallback == "" { |
2281 | + fallback = snd.fallback |
2282 | + } |
2283 | + |
2284 | + sound := notification.Sound(fallback) |
2285 | if sound == "" { |
2286 | snd.log.Debugf("[%s] notification has no Sound: %#v", nid, sound) |
2287 | return false |
2288 | |
2289 | === modified file 'sounds/sounds_test.go' |
2290 | --- sounds/sounds_test.go 2014-08-08 09:03:42 +0000 |
2291 | +++ sounds/sounds_test.go 2015-03-06 13:26:11 +0000 |
2292 | @@ -36,25 +36,42 @@ |
2293 | type soundsSuite struct { |
2294 | log *helpers.TestLogger |
2295 | app *click.AppId |
2296 | + acc *mockAccounts |
2297 | } |
2298 | |
2299 | var _ = Suite(&soundsSuite{}) |
2300 | |
2301 | +type mockAccounts struct { |
2302 | + vib bool |
2303 | + sil bool |
2304 | + snd string |
2305 | + err error |
2306 | +} |
2307 | + |
2308 | +func (m *mockAccounts) Start() error { return m.err } |
2309 | +func (m *mockAccounts) Cancel() error { return m.err } |
2310 | +func (m *mockAccounts) SilentMode() bool { return m.sil } |
2311 | +func (m *mockAccounts) Vibrate() bool { return m.vib } |
2312 | +func (m *mockAccounts) MessageSoundFile() string { return m.snd } |
2313 | +func (m *mockAccounts) String() string { return "<mockAccounts>" } |
2314 | + |
2315 | func (ss *soundsSuite) SetUpTest(c *C) { |
2316 | ss.log = helpers.NewTestLogger(c, "debug") |
2317 | ss.app = clickhelp.MustParseAppId("com.example.test_test_0") |
2318 | + ss.acc = &mockAccounts{true, false, "", nil} |
2319 | } |
2320 | |
2321 | func (ss *soundsSuite) TestNew(c *C) { |
2322 | - s := New(ss.log, "foo") |
2323 | + s := New(ss.log, ss.acc, "foo") |
2324 | c.Check(s.log, Equals, ss.log) |
2325 | c.Check(s.player, Equals, "paplay") |
2326 | c.Check(s.fallback, Equals, "foo") |
2327 | + c.Check(s.acc, Equals, ss.acc) |
2328 | } |
2329 | |
2330 | func (ss *soundsSuite) TestPresent(c *C) { |
2331 | s := &Sound{ |
2332 | - player: "echo", log: ss.log, |
2333 | + player: "echo", log: ss.log, acc: ss.acc, |
2334 | dataFind: func(s string) (string, error) { return s, nil }, |
2335 | } |
2336 | |
2337 | @@ -65,7 +82,7 @@ |
2338 | |
2339 | func (ss *soundsSuite) TestPresentSimple(c *C) { |
2340 | s := &Sound{ |
2341 | - player: "echo", log: ss.log, |
2342 | + player: "echo", log: ss.log, acc: ss.acc, |
2343 | dataFind: func(s string) (string, error) { return s, nil }, |
2344 | fallback: "fallback", |
2345 | } |
2346 | @@ -73,12 +90,18 @@ |
2347 | c.Check(s.Present(ss.app, "", |
2348 | &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true) |
2349 | c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/fallback using echo`) |
2350 | + ss.acc.snd = "from-prefs" |
2351 | + ss.log.ResetCapture() |
2352 | + c.Check(s.Present(ss.app, "", |
2353 | + &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true) |
2354 | + c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/from-prefs using echo`) |
2355 | } |
2356 | |
2357 | func (ss *soundsSuite) TestPresentFails(c *C) { |
2358 | s := &Sound{ |
2359 | player: "/", |
2360 | log: ss.log, |
2361 | + acc: ss.acc, |
2362 | dataFind: func(string) (string, error) { return "", errors.New("nope") }, |
2363 | dataDirs: func() []string { return []string{""} }, |
2364 | } |
2365 | @@ -107,6 +130,7 @@ |
2366 | s := &Sound{ |
2367 | player: "/", |
2368 | log: ss.log, |
2369 | + acc: ss.acc, |
2370 | dataFind: func(string) (string, error) { return "", errors.New("nope") }, |
2371 | dataDirs: func() []string { return []string{""} }, |
2372 | } |
2373 | @@ -120,6 +144,7 @@ |
2374 | s := &Sound{ |
2375 | player: "/", |
2376 | log: ss.log, |
2377 | + acc: ss.acc, |
2378 | dataFind: func(string) (string, error) { return "", errors.New("nope") }, |
2379 | dataDirs: func() []string { return []string{""} }, |
2380 | } |
2381 | @@ -128,3 +153,19 @@ |
2382 | c.Check(err, IsNil) |
2383 | c.Check(sound, Equals, "bar") |
2384 | } |
2385 | + |
2386 | +func (ss *soundsSuite) TestSkipIfSilentMode(c *C) { |
2387 | + s := &Sound{ |
2388 | + player: "echo", |
2389 | + log: ss.log, |
2390 | + acc: ss.acc, |
2391 | + fallback: "fallback", |
2392 | + dataFind: func(s string) (string, error) { return s, nil }, |
2393 | + } |
2394 | + |
2395 | + c.Check(s.Present(ss.app, "", |
2396 | + &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true) |
2397 | + ss.acc.sil = true |
2398 | + c.Check(s.Present(ss.app, "", |
2399 | + &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, false) |
2400 | +} |
2401 | |
2402 | === modified file 'testing/helpers.go' |
2403 | --- testing/helpers.go 2015-01-22 09:52:07 +0000 |
2404 | +++ testing/helpers.go 2015-03-06 13:26:11 +0000 |
2405 | @@ -170,3 +170,12 @@ |
2406 | } |
2407 | return purl |
2408 | } |
2409 | + |
2410 | +// DumpGoroutines dumps current goroutines. |
2411 | +func DumpGoroutines() { |
2412 | + var buf [64 * 1024]byte |
2413 | + sz := runtime.Stack(buf[:], true) |
2414 | + dump := string(buf[:sz]) |
2415 | + fmt.Println(dump) |
2416 | + fmt.Println("#goroutines#", strings.Count("\n"+dump, "\ngoroutine ")) |
2417 | +} |