Merge lp:~ralsina/ubuntu-push/merge-automatic into lp:ubuntu-push

Proposed by Roberto Alsina
Status: Merged
Approved by: Roberto Alsina
Approved revision: no longer in the source branch.
Merged at revision: 119
Proposed branch: lp:~ralsina/ubuntu-push/merge-automatic
Merge into: lp:ubuntu-push
Diff against target: 3047 lines (+1300/-534)
41 files modified
Makefile (+6/-4)
bus/haptic/haptic.go (+10/-11)
bus/haptic/haptic_test.go (+26/-37)
click/click.go (+3/-3)
click/click_test.go (+1/-1)
client/client.go (+23/-10)
client/client_test.go (+37/-7)
client/service/common.go (+3/-3)
client/service/common_test.go (+12/-3)
client/service/postal.go (+16/-4)
client/service/postal_test.go (+57/-47)
client/service/service_test.go (+2/-1)
debian/changelog (+22/-0)
debian/config.json (+3/-1)
debian/ubuntu-push-client.conf (+4/-2)
docs/highlevel.txt (+325/-0)
docs/lowlevel.txt (+11/-12)
identifier/identifier.go (+59/-0)
identifier/identifier_test.go (+54/-0)
identifier/testing/testing.go (+58/-0)
identifier/testing/testing_test.go (+57/-0)
launch_helper/helper_output.go (+70/-15)
launch_helper/helper_output_test.go (+96/-0)
launch_helper/helper_test.go (+1/-1)
launch_helper/kindpool_test.go (+2/-1)
messaging/cmessaging/cmessaging.go (+1/-1)
scripts/connect-many.py (+29/-0)
scripts/goctest (+46/-0)
server/listener/listener_test.go (+34/-22)
sounds/sounds.go (+30/-6)
sounds/sounds_test.go (+45/-5)
tests/autopilot/push_notifications/tests/__init__.py (+51/-1)
tests/autopilot/push_notifications/tests/test_broadcast_notifications.py (+61/-14)
tests/autopilot/push_notifications/tests/test_unicast_notifications.py (+24/-21)
tests/autopilot/run.sh (+6/-2)
tests/autopilot/setup.sh (+15/-5)
whoopsie/doc.go (+0/-18)
whoopsie/identifier/identifier.go (+0/-78)
whoopsie/identifier/identifier_test.go (+0/-58)
whoopsie/identifier/testing/testing.go (+0/-70)
whoopsie/identifier/testing/testing_test.go (+0/-70)
To merge this branch: bzr merge lp:~ralsina/ubuntu-push/merge-automatic
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+230396@code.launchpad.net

Commit message

Merge current automatic branch (see debian/changelog for details)

Description of the change

Merge current automatic branch (see debian/changelog for details)

To post a comment you must log in.
119. By Roberto Alsina

Roberto's branch + fixes to tests that failed in chroot.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2014-06-19 21:31:23 +0000
3+++ Makefile 2014-08-11 21:30:50 +0000
4@@ -13,6 +13,8 @@
5 GODEPS += code.google.com/p/gosqlite/sqlite3
6 GODEPS += code.google.com/p/go-uuid/uuid
7
8+GOTEST := ./scripts/goctest
9+
10 TOTEST = $(shell env GOPATH=$(GOPATH) go list $(PROJECT)/...|grep -v acceptance|grep -v http13client )
11 TOBUILD = $(shell grep -lr '^package main')
12
13@@ -40,10 +42,10 @@
14 $(GOPATH)/bin/godeps -t $(TOTEST) $(foreach i,$(TOBUILD),$(dir $(PROJECT)/$(i))) 2>/dev/null | cat > $@
15
16 check:
17- go test $(TESTFLAGS) $(TOTEST)
18+ $(GOTEST) $(TESTFLAGS) $(TOTEST)
19
20 check-race:
21- go test $(TESTFLAGS) -race $(TOTEST)
22+ $(GOTEST) $(TESTFLAGS) -race $(TOTEST)
23
24 acceptance:
25 cd server/acceptance; ./acceptance.sh
26@@ -83,14 +85,14 @@
27 bzr clean-tree --verbose --ignored --force
28
29 coverage-summary:
30- go test $(TESTFLAGS) -a -cover $(TOTEST)
31+ $(GOTEST) $(TESTFLAGS) -a -cover $(TOTEST)
32
33 coverage-html:
34 mkdir -p coverhtml
35 for pkg in $(TOTEST); do \
36 relname="$${pkg#$(PROJECT)/}" ; \
37 mkdir -p coverhtml/$$(dirname $${relname}) ; \
38- go test $(TESTFLAGS) -a -coverprofile=coverhtml/$${relname}.out $$pkg ; \
39+ $(GOTEST) $(TESTFLAGS) -a -coverprofile=coverhtml/$${relname}.out $$pkg ; \
40 if [ -f coverhtml/$${relname}.out ] ; then \
41 go tool cover -html=coverhtml/$${relname}.out -o coverhtml/$${relname}.html ; \
42 go tool cover -func=coverhtml/$${relname}.out -o coverhtml/$${relname}.txt ; \
43
44=== modified file 'bus/haptic/haptic.go'
45--- bus/haptic/haptic.go 2014-07-25 10:25:59 +0000
46+++ bus/haptic/haptic.go 2014-08-11 21:30:50 +0000
47@@ -34,13 +34,14 @@
48
49 // Haptic encapsulates info needed to call out to usensord/haptic
50 type Haptic struct {
51- bus bus.Endpoint
52- log logger.Logger
53+ bus bus.Endpoint
54+ log logger.Logger
55+ fallback *launch_helper.Vibration
56 }
57
58 // New returns a new Haptic that'll use the provided bus.Endpoint
59-func New(endp bus.Endpoint, log logger.Logger) *Haptic {
60- return &Haptic{endp, log}
61+func New(endp bus.Endpoint, log logger.Logger, fallback *launch_helper.Vibration) *Haptic {
62+ return &Haptic{endp, log, fallback}
63 }
64
65 // Present presents the notification via a vibrate pattern
66@@ -49,18 +50,16 @@
67 panic("please check notification is not nil before calling present")
68 }
69
70- if notification.Vibrate == nil {
71- haptic.log.Debugf("[%s] notification has no Vibrate: %#v", nid, notification.Vibrate)
72+ vib := notification.Vibration(haptic.fallback)
73+ if vib == nil {
74+ haptic.log.Debugf("[%s] notification has no Vibrate.", nid)
75 return false
76 }
77- pattern := notification.Vibrate.Pattern
78- repeat := notification.Vibrate.Repeat
79+ pattern := vib.Pattern
80+ repeat := vib.Repeat
81 if repeat == 0 {
82 repeat = 1
83 }
84- if notification.Vibrate.Duration != 0 {
85- pattern = []uint32{notification.Vibrate.Duration}
86- }
87 if len(pattern) == 0 {
88 haptic.log.Debugf("[%s] not enough information in the Vibrate to create a pattern", nid)
89 return false
90
91=== modified file 'bus/haptic/haptic_test.go'
92--- bus/haptic/haptic_test.go 2014-07-25 10:25:59 +0000
93+++ bus/haptic/haptic_test.go 2014-08-11 21:30:50 +0000
94@@ -17,6 +17,7 @@
95 package haptic
96
97 import (
98+ "encoding/json"
99 "testing"
100
101 . "launchpad.net/gocheck"
102@@ -47,8 +48,8 @@
103 func (hs *hapticSuite) TestPresentPresents(c *C) {
104 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
105
106- ec := New(endp, hs.log)
107- notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}}
108+ ec := New(endp, hs.log, nil)
109+ notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100], "repeat": 2}`)}
110 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
111 callArgs := testibus.GetCallArgs(endp)
112 c.Assert(callArgs, HasLen, 1)
113@@ -60,9 +61,9 @@
114 func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) {
115 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
116
117- ec := New(endp, hs.log)
118+ ec := New(endp, hs.log, nil)
119 // note: no Repeat:
120- notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Pattern: []uint32{200, 100}}}
121+ notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100]}`)}
122 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
123 callArgs := testibus.GetCallArgs(endp)
124 c.Assert(callArgs, HasLen, 1)
125@@ -71,52 +72,40 @@
126 c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(1)})
127 }
128
129-// check that Present() makes a Pattern of [Duration] if Duration is given
130-func (hs *hapticSuite) TestPresentBuildsPatternWithDuration(c *C) {
131- endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
132-
133- ec := New(endp, hs.log)
134- // note: no Repeat, no Pattern, just Duration:
135- notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200}}
136- c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
137- callArgs := testibus.GetCallArgs(endp)
138- c.Assert(callArgs, HasLen, 1)
139- c.Check(callArgs[0].Member, Equals, "VibratePattern")
140- // note: Pattern of [Duration], Repeat of 1:
141- c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)})
142-}
143-
144-// check that Present() ignores Pattern and makes a Pattern of [Duration] if Duration is given
145-func (hs *hapticSuite) TestPresentOverrides(c *C) {
146- endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
147-
148- ec := New(endp, hs.log)
149- // note: Duration given, as well as Pattern; Repeat given as 0:
150- notif := launch_helper.Notification{Vibrate: &launch_helper.Vibration{Duration: 200, Pattern: []uint32{500}, Repeat: 0}}
151- c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
152- callArgs := testibus.GetCallArgs(endp)
153- c.Assert(callArgs, HasLen, 1)
154- c.Check(callArgs[0].Member, Equals, "VibratePattern")
155- // note: Pattern of [Duration], Repeat of 1:
156- c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200}, uint32(1)})
157-}
158-
159 // check that Present() doesn't call VibratePattern if things are not right
160 func (hs *hapticSuite) TestSkipIfMissing(c *C) {
161 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
162
163- ec := New(endp, hs.log)
164+ ec := New(endp, hs.log, nil)
165 // no Vibration in the notificaton
166 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false)
167 // empty Vibration
168- c.Check(ec.Present(hs.app, "", &launch_helper.Notification{Vibrate: &launch_helper.Vibration{}}), Equals, false)
169+ c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: nil}), Equals, false)
170+ // empty empty vibration
171+ c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: json.RawMessage(`{}`)}), Equals, false)
172 }
173
174 // check that Present() panics if the notification is nil
175 func (hs *hapticSuite) TestPanicsIfNil(c *C) {
176 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
177
178- ec := New(endp, hs.log)
179+ ec := New(endp, hs.log, nil)
180 // no notification at all
181 c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`)
182 }
183+
184+// check that Present() uses the fallback if appropriate
185+func (hs *hapticSuite) TestPresentPresentsFallback(c *C) {
186+ endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
187+ fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}
188+
189+ ec := New(endp, hs.log, fallback)
190+ notif := launch_helper.Notification{RawVibration: json.RawMessage(`false`)}
191+ c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)
192+ notif = launch_helper.Notification{RawVibration: json.RawMessage(`true`)}
193+ c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
194+ callArgs := testibus.GetCallArgs(endp)
195+ c.Assert(callArgs, HasLen, 1)
196+ c.Check(callArgs[0].Member, Equals, "VibratePattern")
197+ c.Check(callArgs[0].Args, DeepEquals, []interface{}{[]uint32{200, 100}, uint32(2)})
198+}
199
200=== modified file 'click/click.go'
201--- click/click.go 2014-07-29 15:36:00 +0000
202+++ click/click.go 2014-08-11 21:30:50 +0000
203@@ -51,7 +51,7 @@
204
205 var (
206 ErrInvalidAppId = errors.New("invalid application id")
207- ErrMissingAppId = errors.New("missing application id")
208+ ErrMissingApp = errors.New("application not installed")
209 )
210
211 func ParseAppId(id string) (*AppId, error) {
212@@ -161,14 +161,14 @@
213
214 // ParseAndVerifyAppId parses the given app id and checks if the
215 // corresponding app is installed, returning the parsed id or
216-// ErrInvalidAppId, or the parsed id and ErrMissingAppId respectively.
217+// ErrInvalidAppId, or the parsed id and ErrMissingApp respectively.
218 func ParseAndVerifyAppId(id string, installedChecker InstalledChecker) (*AppId, error) {
219 app, err := ParseAppId(id)
220 if err != nil {
221 return nil, err
222 }
223 if installedChecker != nil && !installedChecker.Installed(app, true) {
224- return app, ErrMissingAppId
225+ return app, ErrMissingApp
226 }
227 return app, nil
228 }
229
230=== modified file 'click/click_test.go'
231--- click/click_test.go 2014-07-29 15:36:00 +0000
232+++ click/click_test.go 2014-08-11 21:30:50 +0000
233@@ -178,7 +178,7 @@
234 c.Check(app.Application, Equals, "baz")
235
236 app, err = ParseAndVerifyAppId("_non-existent-app", u)
237- c.Assert(err, Equals, ErrMissingAppId)
238+ c.Assert(err, Equals, ErrMissingApp)
239 c.Check(app, NotNil)
240 c.Check(app.Original(), Equals, "_non-existent-app")
241 }
242
243=== modified file 'client/client.go'
244--- client/client.go 2014-07-21 17:38:28 +0000
245+++ client/client.go 2014-08-11 21:30:50 +0000
246@@ -41,10 +41,11 @@
247 "launchpad.net/ubuntu-push/client/session"
248 "launchpad.net/ubuntu-push/client/session/seenstate"
249 "launchpad.net/ubuntu-push/config"
250+ "launchpad.net/ubuntu-push/identifier"
251+ "launchpad.net/ubuntu-push/launch_helper"
252 "launchpad.net/ubuntu-push/logger"
253 "launchpad.net/ubuntu-push/protocol"
254 "launchpad.net/ubuntu-push/util"
255- "launchpad.net/ubuntu-push/whoopsie/identifier"
256 )
257
258 // ClientConfig holds the client configuration
259@@ -67,6 +68,9 @@
260 RegistrationURL string `json:"registration_url"`
261 // The logging level (one of "debug", "info", "error")
262 LogLevel logger.ConfigLogLevel `json:"log_level"`
263+ // fallback values for simplified notification usage
264+ FallbackVibration *launch_helper.Vibration `json:"fallback_vibration"`
265+ FallbackSound string `json:"fallback_sound"`
266 }
267
268 // PushService is the interface we use of service.PushService.
269@@ -152,7 +156,10 @@
270 client.unregisterCh = make(chan *click.AppId, 10)
271
272 // overridden for testing
273- client.idder = identifier.New()
274+ client.idder, err = identifier.New()
275+ if err != nil {
276+ return err
277+ }
278 client.connectivityEndp = bus.SystemBus.Endpoint(networkmanager.BusAddress, client.log)
279 client.systemImageEndp = bus.SystemBus.Endpoint(systemimage.BusAddress, client.log)
280
281@@ -203,6 +210,15 @@
282 return setup, nil
283 }
284
285+// derivePostalServiceSetup derives the service setup from the client configuration bits.
286+func (client *PushClient) derivePostalServiceSetup() *service.PostalServiceSetup {
287+ return &service.PostalServiceSetup{
288+ InstalledChecker: client.installedChecker,
289+ FallbackVibration: client.config.FallbackVibration,
290+ FallbackSound: client.config.FallbackSound,
291+ }
292+}
293+
294 // getAuthorization gets the authorization blob to send to the server
295 func (client *PushClient) getAuthorization(url string) string {
296 client.log.Debugf("getting authorization for %s", url)
297@@ -222,16 +238,12 @@
298 }
299 }
300
301-// getDeviceId gets the whoopsie identifier for the device
302+// getDeviceId gets the identifier for the device
303 func (client *PushClient) getDeviceId() error {
304- err := client.idder.Generate()
305- if err != nil {
306- return err
307- }
308 baseId := client.idder.String()
309 b, err := hex.DecodeString(baseId)
310 if err != nil {
311- return fmt.Errorf("whoopsie id should be hex: %v", err)
312+ return fmt.Errorf("machine-id should be hex: %v", err)
313 }
314 h := sha256.Sum224(b)
315 client.deviceId = base64.StdEncoding.EncodeToString(h[:])
316@@ -294,7 +306,7 @@
317 switch err {
318 default:
319 client.log.Debugf("notification %#v for invalid app id %#v.", notif.MsgId, notif.AppId)
320- case click.ErrMissingAppId:
321+ case click.ErrMissingApp:
322 client.log.Debugf("notification %#v for missing app id %#v.", notif.MsgId, notif.AppId)
323 client.unregisterCh <- parsed
324 parsed = nil
325@@ -473,7 +485,8 @@
326 }
327
328 func (client *PushClient) setupPostalService() error {
329- client.postalService = service.NewPostalService(client.installedChecker, client.log)
330+ setup := client.derivePostalServiceSetup()
331+ client.postalService = service.NewPostalService(setup, client.log)
332 return nil
333 }
334
335
336=== modified file 'client/client_test.go'
337--- client/client_test.go 2014-07-21 17:38:28 +0000
338+++ client/client_test.go 2014-08-11 21:30:50 +0000
339@@ -42,12 +42,13 @@
340 "launchpad.net/ubuntu-push/client/session"
341 "launchpad.net/ubuntu-push/client/session/seenstate"
342 "launchpad.net/ubuntu-push/config"
343+ "launchpad.net/ubuntu-push/identifier"
344+ idtesting "launchpad.net/ubuntu-push/identifier/testing"
345+ "launchpad.net/ubuntu-push/launch_helper"
346 "launchpad.net/ubuntu-push/protocol"
347 helpers "launchpad.net/ubuntu-push/testing"
348 "launchpad.net/ubuntu-push/testing/condition"
349 "launchpad.net/ubuntu-push/util"
350- "launchpad.net/ubuntu-push/whoopsie/identifier"
351- idtesting "launchpad.net/ubuntu-push/whoopsie/identifier/testing"
352 )
353
354 func TestClient(t *testing.T) { TestingT(t) }
355@@ -154,6 +155,8 @@
356 func (cs *clientSuite) writeTestConfig(overrides map[string]interface{}) {
357 pem_file := helpers.SourceRelative("../server/acceptance/ssl/testing.cert")
358 cfgMap := map[string]interface{}{
359+ "fallback_vibration": &launch_helper.Vibration{Pattern: []uint32{1}},
360+ "fallback_sound": "sounds/ubuntu/notifications/Blip.ogg",
361 "connect_timeout": "7ms",
362 "exchange_timeout": "10ms",
363 "hosts_cache_expiry": "1h",
364@@ -239,7 +242,8 @@
365 c.Check(cli.idder, IsNil)
366 err := cli.configure()
367 c.Assert(err, IsNil)
368- c.Assert(cli.idder, FitsTypeOf, identifier.New())
369+ newIdder, err := identifier.New()
370+ c.Assert(cli.idder, FitsTypeOf, newIdder)
371 }
372
373 func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) {
374@@ -471,6 +475,32 @@
375 }
376
377 /*****************************************************************
378+ derivePostalConfig tests
379+******************************************************************/
380+func (cs *clientSuite) TestDerivePostalServiceSetup(c *C) {
381+ cs.writeTestConfig(map[string]interface{}{})
382+ cli := NewPushClient(cs.configPath, cs.leveldbPath)
383+ err := cli.configure()
384+ c.Assert(err, IsNil)
385+ expected := &service.PostalServiceSetup{
386+ InstalledChecker: cli.installedChecker,
387+ FallbackVibration: cli.config.FallbackVibration,
388+ FallbackSound: cli.config.FallbackSound,
389+ }
390+ // sanity check that we are looking at all fields
391+ vExpected := reflect.ValueOf(expected).Elem()
392+ nf := vExpected.NumField()
393+ for i := 0; i < nf; i++ {
394+ fv := vExpected.Field(i)
395+ // field isn't empty/zero
396+ c.Assert(fv.Interface(), Not(DeepEquals), reflect.Zero(fv.Type()).Interface(), Commentf("forgot about: %s", vExpected.Type().Field(i).Name))
397+ }
398+ // finally compare
399+ setup := cli.derivePostalServiceSetup()
400+ c.Check(setup, DeepEquals, expected)
401+}
402+
403+/*****************************************************************
404 startService tests
405 ******************************************************************/
406
407@@ -536,7 +566,7 @@
408 func (cs *clientSuite) TestGetDeviceIdWorks(c *C) {
409 cli := NewPushClient(cs.configPath, cs.leveldbPath)
410 cli.log = cs.log
411- cli.idder = identifier.New()
412+ cli.idder, _ = identifier.New()
413 c.Check(cli.deviceId, Equals, "")
414 c.Check(cli.getDeviceId(), IsNil)
415 c.Check(cli.deviceId, HasLen, 40)
416@@ -550,14 +580,14 @@
417 c.Check(cli.getDeviceId(), NotNil)
418 }
419
420-func (cs *clientSuite) TestGetDeviceIdWhoopsieDoesTheUnexpected(c *C) {
421+func (cs *clientSuite) TestGetDeviceIdIdentifierDoesTheUnexpected(c *C) {
422 cli := NewPushClient(cs.configPath, cs.leveldbPath)
423 cli.log = cs.log
424 settable := idtesting.Settable()
425 cli.idder = settable
426 settable.Set("not-hex")
427 c.Check(cli.deviceId, Equals, "")
428- c.Check(cli.getDeviceId(), ErrorMatches, "whoopsie id should be hex: .*")
429+ c.Check(cli.getDeviceId(), ErrorMatches, "machine-id should be hex: .*")
430 }
431
432 /*****************************************************************
433@@ -1111,7 +1141,7 @@
434 // and now everthing is better! We have a config,
435 c.Check(string(cli.config.Addr), Equals, ":0")
436 // and a device id,
437- c.Check(cli.deviceId, HasLen, 40)
438+ c.Check(cli.deviceId, HasLen, 32)
439 // and a session,
440 c.Check(cli.session, NotNil)
441 // and a bus,
442
443=== modified file 'client/service/common.go'
444--- client/service/common.go 2014-07-15 20:53:41 +0000
445+++ client/service/common.go 2014-08-11 21:30:50 +0000
446@@ -52,7 +52,7 @@
447 ErrBadArgCount = errors.New("wrong number of arguments")
448 ErrBadArgType = errors.New("bad argument type")
449 ErrBadJSON = errors.New("bad json data")
450- ErrBadAppId = errors.New("package must be prefix of app id")
451+ ErrAppIdMismatch = errors.New("package must be prefix of app id")
452 )
453
454 // IsRunning() returns whether the service's state is StateRunning
455@@ -118,10 +118,10 @@
456 pkgname := string(nih.Unquote([]byte(path[strings.LastIndex(path, "/")+1:])))
457 app, err = click.ParseAndVerifyAppId(id, svc.installedChecker)
458 if err != nil {
459- return nil, ErrBadAppId
460+ return nil, err
461 }
462 if !app.InPackage(pkgname) {
463- return nil, ErrBadAppId
464+ return nil, ErrAppIdMismatch
465 }
466 return
467 }
468
469=== modified file 'client/service/common_test.go'
470--- client/service/common_test.go 2014-07-08 08:32:32 +0000
471+++ client/service/common_test.go 2014-08-11 21:30:50 +0000
472@@ -18,6 +18,8 @@
473
474 import (
475 . "launchpad.net/gocheck"
476+
477+ "launchpad.net/ubuntu-push/click"
478 )
479
480 type commonSuite struct{}
481@@ -35,10 +37,16 @@
482 c.Check(app.Original(), Equals, anAppId)
483 }
484
485+type fakeInstalledChecker struct{}
486+
487+func (fakeInstalledChecker) Installed(app *click.AppId, setVersion bool) bool {
488+ return app.Original()[0] == 'c'
489+}
490+
491 func (cs *commonSuite) TestGrabDBusPackageAndAppIdFails(c *C) {
492 svc := new(DBusService)
493+ svc.installedChecker = fakeInstalledChecker{}
494 aDBusPath := "/com/ubuntu/Postal/com_2eexample_2etest"
495- aDBusPath2 := "/com/ubuntu/Postal/com_2efoo_2ebar"
496 aPackage := "com.example.test"
497 anAppId := aPackage + "_test"
498
499@@ -52,8 +60,9 @@
500 {aDBusPath, []interface{}{anAppId}, 1, ErrBadArgCount},
501 {aDBusPath, []interface{}{anAppId, anAppId}, 0, ErrBadArgCount},
502 {aDBusPath, []interface{}{1}, 0, ErrBadArgType},
503- {aDBusPath, []interface{}{aPackage}, 0, ErrBadAppId},
504- {aDBusPath2, []interface{}{anAppId}, 0, ErrBadAppId},
505+ {aDBusPath, []interface{}{aPackage}, 0, click.ErrInvalidAppId},
506+ {aDBusPath, []interface{}{"x" + anAppId}, 0, click.ErrMissingApp},
507+ {aDBusPath, []interface{}{"c" + anAppId}, 0, ErrAppIdMismatch},
508 } {
509 comment := Commentf("iteration #%d", i)
510 app, err := svc.grabDBusPackageAndAppId(s.path, s.args, s.numExtra)
511
512=== modified file 'client/service/postal.go'
513--- client/service/postal.go 2014-08-01 14:17:04 +0000
514+++ client/service/postal.go 2014-08-11 21:30:50 +0000
515@@ -56,6 +56,13 @@
516 Clear(*click.AppId, ...string) int
517 }
518
519+// PostalServiceSetup is a configuration object for the service
520+type PostalServiceSetup struct {
521+ InstalledChecker click.InstalledChecker
522+ FallbackVibration *launch_helper.Vibration
523+ FallbackSound string
524+}
525+
526 // PostalService is the dbus api
527 type PostalService struct {
528 DBusService
529@@ -80,6 +87,9 @@
530 // the url dispatcher, used for stuff.
531 urlDispatcher urldispatcher.URLDispatcher
532 windowStack *windowstack.WindowStack
533+ // fallback values for simplified notification usage
534+ fallbackVibration *launch_helper.Vibration
535+ fallbackSound string
536 }
537
538 var (
539@@ -96,11 +106,13 @@
540 )
541
542 // NewPostalService() builds a new service and returns it.
543-func NewPostalService(installedChecker click.InstalledChecker, log logger.Logger) *PostalService {
544+func NewPostalService(setup *PostalServiceSetup, log logger.Logger) *PostalService {
545 var svc = &PostalService{}
546 svc.Log = log
547 svc.Bus = bus.SessionBus.Endpoint(PostalServiceBusAddress, log)
548- svc.installedChecker = installedChecker
549+ svc.installedChecker = setup.InstalledChecker
550+ svc.fallbackVibration = setup.FallbackVibration
551+ svc.fallbackSound = setup.FallbackSound
552 svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log)
553 svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log)
554 svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log)
555@@ -144,8 +156,8 @@
556 svc.urlDispatcher = urldispatcher.New(svc.URLDispatcherEndp, svc.Log)
557 svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log)
558 svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log)
559- svc.haptic = haptic.New(svc.HapticEndp, svc.Log)
560- svc.sound = sounds.New(svc.Log)
561+ svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.fallbackVibration)
562+ svc.sound = sounds.New(svc.Log, svc.fallbackSound)
563 svc.messagingMenu = messaging.New(svc.Log)
564 svc.Presenters = []Presenter{
565 svc.notifications,
566
567=== modified file 'client/service/postal_test.go'
568--- client/service/postal_test.go 2014-08-01 14:17:04 +0000
569+++ client/service/postal_test.go 2014-08-11 21:30:50 +0000
570@@ -133,6 +133,7 @@
571
572 type postalSuite struct {
573 log *helpers.TestLogger
574+ cfg *PostalServiceSetup
575 bus bus.Endpoint
576 notifBus bus.Endpoint
577 counterBus bus.Endpoint
578@@ -160,6 +161,7 @@
579 ps.oldIsBlisted = isBlacklisted
580 isBlacklisted = func(*click.AppId) bool { return ps.blacklisted }
581 ps.log = helpers.NewTestLogger(c, "debug")
582+ ps.cfg = &PostalServiceSetup{}
583 ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
584 ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
585 ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
586@@ -204,7 +206,7 @@
587 }
588
589 func (ps *postalSuite) TestStart(c *C) {
590- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
591+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
592 c.Check(svc.IsRunning(), Equals, false)
593 c.Check(svc.Start(), IsNil)
594 c.Check(svc.IsRunning(), Equals, true)
595@@ -212,30 +214,30 @@
596 }
597
598 func (ps *postalSuite) TestStartTwice(c *C) {
599- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
600+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
601 c.Check(svc.Start(), IsNil)
602 c.Check(svc.Start(), Equals, ErrAlreadyStarted)
603 svc.Stop()
604 }
605
606 func (ps *postalSuite) TestStartNoLog(c *C) {
607- svc := ps.replaceBuses(NewPostalService(nil, nil))
608+ svc := ps.replaceBuses(NewPostalService(ps.cfg, nil))
609 c.Check(svc.Start(), Equals, ErrNotConfigured)
610 }
611
612 func (ps *postalSuite) TestStartNoBus(c *C) {
613- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
614+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
615 svc.Bus = nil
616 c.Check(svc.Start(), Equals, ErrNotConfigured)
617
618- svc = ps.replaceBuses(NewPostalService(nil, ps.log))
619+ svc = ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
620 svc.NotificationsEndp = nil
621 c.Check(svc.Start(), Equals, ErrNotConfigured)
622 }
623
624 func (ps *postalSuite) TestTakeTheBusFail(c *C) {
625 nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(false))
626- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
627+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
628 svc.NotificationsEndp = nEndp
629 _, err := svc.takeTheBus()
630 c.Check(err, NotNil)
631@@ -243,7 +245,7 @@
632
633 func (ps *postalSuite) TestTakeTheBusOk(c *C) {
634 nEndp := testibus.NewMultiValuedTestingEndpoint(condition.Work(true), condition.Work(true), []interface{}{uint32(1), "hello"})
635- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
636+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
637 svc.NotificationsEndp = nEndp
638 _, err := svc.takeTheBus()
639 c.Check(err, IsNil)
640@@ -251,14 +253,14 @@
641
642 func (ps *postalSuite) TestStartFailsOnBusDialFailure(c *C) {
643 // XXX actually, we probably want to autoredial this
644- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
645+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
646 svc.Bus = testibus.NewTestingEndpoint(condition.Work(false), nil)
647 c.Check(svc.Start(), ErrorMatches, `.*(?i)cond said no.*`)
648 svc.Stop()
649 }
650
651 func (ps *postalSuite) TestStartGrabsName(c *C) {
652- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
653+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
654 c.Assert(svc.Start(), IsNil)
655 callArgs := testibus.GetCallArgs(ps.bus)
656 defer svc.Stop()
657@@ -267,7 +269,7 @@
658 }
659
660 func (ps *postalSuite) TestStopClosesBus(c *C) {
661- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
662+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
663 c.Assert(svc.Start(), IsNil)
664 svc.Stop()
665 callArgs := testibus.GetCallArgs(ps.bus)
666@@ -279,7 +281,7 @@
667 // post() tests
668
669 func (ps *postalSuite) TestPostHappyPath(c *C) {
670- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
671+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
672 svc.msgHandler = nil
673 ch := installTickMessageHandler(svc)
674 svc.launchers = map[string]launch_helper.HelperLauncher{
675@@ -322,7 +324,7 @@
676 {[]interface{}{anAppId, "zoom"}, ErrBadJSON},
677 {[]interface{}{1, "hello"}, ErrBadArgType},
678 {[]interface{}{1, 2, 3}, ErrBadArgCount},
679- {[]interface{}{"bar", "hello"}, ErrBadAppId},
680+ {[]interface{}{"bar", "hello"}, click.ErrInvalidAppId},
681 } {
682 reg, err := new(PostalService).post(aPackageOnBus, s.args, nil)
683 c.Check(reg, IsNil, Commentf("iteration #%d", i))
684@@ -333,7 +335,7 @@
685 // Post() tests
686
687 func (ps *postalSuite) TestPostWorks(c *C) {
688- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
689+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
690 svc.msgHandler = nil
691 ch := installTickMessageHandler(svc)
692 fakeLauncher2 := &fakeHelperLauncher{ch: make(chan []byte)}
693@@ -384,7 +386,7 @@
694
695 func (ps *postalSuite) TestPostCallsMessageHandlerDetails(c *C) {
696 ch := make(chan *launch_helper.HelperOutput)
697- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
698+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
699 svc.launchers = map[string]launch_helper.HelperLauncher{
700 "click": ps.fakeLauncher,
701 }
702@@ -410,7 +412,7 @@
703 }
704
705 func (ps *postalSuite) TestAfterMessageHandlerSignal(c *C) {
706- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
707+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
708 svc.msgHandler = nil
709
710 hInp := &launch_helper.HelperInput{
711@@ -431,7 +433,7 @@
712 }
713
714 func (ps *postalSuite) TestFailingMessageHandlerSurvived(c *C) {
715- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
716+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
717 svc.SetMessageHandler(func(*click.AppId, string, *launch_helper.HelperOutput) bool {
718 return false
719 })
720@@ -453,7 +455,7 @@
721 //
722 // Notifications tests
723 func (ps *postalSuite) TestNotificationsWorks(c *C) {
724- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
725+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
726 nots, err := svc.popAll(aPackageOnBus, []interface{}{anAppId}, nil)
727 c.Assert(err, IsNil)
728 c.Assert(nots, NotNil)
729@@ -487,7 +489,7 @@
730 {nil, ErrBadArgCount},
731 {[]interface{}{}, ErrBadArgCount},
732 {[]interface{}{1}, ErrBadArgType},
733- {[]interface{}{"potato"}, ErrBadAppId},
734+ {[]interface{}{"potato"}, click.ErrInvalidAppId},
735 } {
736 reg, err := new(PostalService).popAll(aPackageOnBus, s.args, nil)
737 c.Check(reg, IsNil, Commentf("iteration #%d", i))
738@@ -510,7 +512,7 @@
739
740 func (ps *postalSuite) TestMessageHandlerPresents(c *C) {
741 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(1))
742- svc := NewPostalService(nil, ps.log)
743+ svc := NewPostalService(ps.cfg, ps.log)
744 svc.Bus = endp
745 svc.EmblemCounterEndp = endp
746 svc.HapticEndp = endp
747@@ -518,13 +520,14 @@
748 svc.URLDispatcherEndp = ps.urlDispBus
749 svc.WindowStackEndp = ps.winStackBus
750 svc.launchers = map[string]launch_helper.HelperLauncher{}
751+ svc.fallbackVibration = &launch_helper.Vibration{Pattern: []uint32{1}}
752 c.Assert(svc.Start(), IsNil)
753
754 // Persist is false so we just check the log
755 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false}
756- vib := &launch_helper.Vibration{Duration: 500}
757+ vib := json.RawMessage(`true`)
758 emb := &launch_helper.EmblemCounter{Count: 2, Visible: true}
759- output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, Vibrate: vib}}
760+ output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{Card: card, EmblemCounter: emb, RawVibration: vib}}
761 b := svc.messageHandler(&click.AppId{}, "", output)
762 c.Assert(b, Equals, true)
763 args := testibus.GetCallArgs(endp)
764@@ -547,7 +550,7 @@
765
766 func (ps *postalSuite) TestMessageHandlerReportsFailedNotifies(c *C) {
767 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), 1)
768- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
769+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
770 svc.NotificationsEndp = endp
771 c.Assert(svc.Start(), IsNil)
772 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true}
773@@ -559,7 +562,7 @@
774
775 func (ps *postalSuite) TestMessageHandlerInhibition(c *C) {
776 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{{0, "com.example.test_test-app", true, 0}})
777- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
778+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
779 svc.WindowStackEndp = endp
780 c.Assert(svc.Start(), IsNil)
781 output := &launch_helper.HelperOutput{Notification: &launch_helper.Notification{}} // Doesn't matter
782@@ -569,7 +572,7 @@
783 }
784
785 func (ps *postalSuite) TestMessageHandlerReportsButIgnoresUnmarshalErrors(c *C) {
786- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
787+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
788 c.Assert(svc.Start(), IsNil)
789 output := &launch_helper.HelperOutput{[]byte(`broken`), nil}
790 b := svc.messageHandler(nil, "", output)
791@@ -579,7 +582,7 @@
792
793 func (ps *postalSuite) TestMessageHandlerReportsButIgnoresNilNotifies(c *C) {
794 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false))
795- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
796+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
797 c.Assert(svc.Start(), IsNil)
798 svc.NotificationsEndp = endp
799 output := &launch_helper.HelperOutput{[]byte(`{}`), nil}
800@@ -589,7 +592,7 @@
801 }
802
803 func (ps *postalSuite) TestMessageHandlerInvalidAction(c *C) {
804- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
805+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
806 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(false), []string{"com.example.test_test-app"})
807 svc.URLDispatcherEndp = endp
808 c.Assert(svc.Start(), IsNil)
809@@ -601,7 +604,7 @@
810 }
811
812 func (ps *postalSuite) TestHandleActionsDispatches(c *C) {
813- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
814+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
815 fmm := new(fakeMM)
816 app, _ := click.ParseAppId("com.example.test_test-app")
817 c.Assert(svc.Start(), IsNil)
818@@ -627,7 +630,7 @@
819 }
820
821 func (ps *postalSuite) TestHandleMMUActionsDispatches(c *C) {
822- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
823+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
824 c.Assert(svc.Start(), IsNil)
825 app, _ := click.ParseAppId("com.example.test_test-app")
826 aCh := make(chan *notifications.RawAction)
827@@ -650,7 +653,7 @@
828 }
829
830 func (ps *postalSuite) TestValidateActions(c *C) {
831- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
832+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
833 endp := testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []string{"com.example.test_test-app_0"})
834 svc.URLDispatcherEndp = endp
835 c.Assert(svc.Start(), IsNil)
836@@ -661,7 +664,7 @@
837 }
838
839 func (ps *postalSuite) TestValidateActionsNoActions(c *C) {
840- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
841+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
842 card := launch_helper.Card{}
843 notif := &launch_helper.Notification{Card: &card}
844 b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif)
845@@ -669,7 +672,7 @@
846 }
847
848 func (ps *postalSuite) TestValidateActionsNoCard(c *C) {
849- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
850+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
851 notif := &launch_helper.Notification{}
852 b := svc.validateActions(clickhelp.MustParseAppId("com.example.test_test-app_0"), notif)
853 c.Check(b, Equals, true)
854@@ -695,7 +698,7 @@
855 }
856
857 func (ps *postalSuite) TestListPersistent(c *C) {
858- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
859+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
860 fmm := new(fakeMM)
861 svc.messagingMenu = fmm
862
863@@ -709,17 +712,24 @@
864 }
865
866 func (ps *postalSuite) TestListPersistentErrors(c *C) {
867- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
868- _, err := svc.listPersistent(aPackageOnBus, nil, nil)
869- c.Check(err, Equals, ErrBadArgCount)
870- _, err = svc.listPersistent(aPackageOnBus, []interface{}{42}, nil)
871- c.Check(err, Equals, ErrBadArgType)
872- _, err = svc.listPersistent(aPackageOnBus, []interface{}{"xyzzy"}, nil)
873- c.Check(err, Equals, ErrBadAppId)
874+ for i, s := range []struct {
875+ args []interface{}
876+ errt error
877+ }{
878+ {nil, ErrBadArgCount},
879+ {[]interface{}{}, ErrBadArgCount},
880+ {[]interface{}{1}, ErrBadArgType},
881+ {[]interface{}{anAppId, 2}, ErrBadArgCount},
882+ {[]interface{}{"bar"}, click.ErrInvalidAppId},
883+ } {
884+ reg, err := new(PostalService).listPersistent(aPackageOnBus, s.args, nil)
885+ c.Check(reg, IsNil, Commentf("iteration #%d", i))
886+ c.Check(err, Equals, s.errt, Commentf("iteration #%d", i))
887+ }
888 }
889
890 func (ps *postalSuite) TestClearPersistent(c *C) {
891- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
892+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
893 fmm := new(fakeMM)
894 svc.messagingMenu = fmm
895
896@@ -730,24 +740,23 @@
897 }
898
899 func (ps *postalSuite) TestClearPersistentErrors(c *C) {
900- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
901 for i, s := range []struct {
902 args []interface{}
903 err error
904 }{
905 {[]interface{}{}, ErrBadArgCount},
906 {[]interface{}{42}, ErrBadArgType},
907- {[]interface{}{"xyzzy"}, ErrBadAppId},
908+ {[]interface{}{"xyzzy"}, click.ErrInvalidAppId},
909 {[]interface{}{anAppId, 42}, ErrBadArgType},
910 {[]interface{}{anAppId, "", 42}, ErrBadArgType},
911 } {
912- _, err := svc.clearPersistent(aPackageOnBus, s.args, nil)
913+ _, err := new(PostalService).clearPersistent(aPackageOnBus, s.args, nil)
914 c.Check(err, Equals, s.err, Commentf("iter %d", i))
915 }
916 }
917
918 func (ps *postalSuite) TestSetCounter(c *C) {
919- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
920+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
921 c.Check(svc.Start(), IsNil)
922
923 _, err := svc.setCounter(aPackageOnBus, []interface{}{anAppId, int32(42), true}, nil)
924@@ -765,8 +774,9 @@
925 }
926
927 func (ps *postalSuite) TestSetCounterErrors(c *C) {
928- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
929+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
930 svc.Start()
931+
932 for i, s := range []struct {
933 args []interface{}
934 err error
935@@ -776,7 +786,7 @@
936 {[]interface{}{anAppId}, ErrBadArgCount},
937 {[]interface{}{anAppId, int32(42)}, ErrBadArgCount},
938 {[]interface{}{anAppId, int32(42), true, "potato"}, ErrBadArgCount},
939- {[]interface{}{"xyzzy", int32(42), true}, ErrBadAppId},
940+ {[]interface{}{"xyzzy", int32(42), true}, click.ErrInvalidAppId},
941 {[]interface{}{1234567, int32(42), true}, ErrBadArgType},
942 {[]interface{}{anAppId, "potatoe", true}, ErrBadArgType},
943 {[]interface{}{anAppId, int32(42), "ru"}, ErrBadArgType},
944@@ -787,7 +797,7 @@
945 }
946
947 func (ps *postalSuite) TestBlacklisted(c *C) {
948- svc := ps.replaceBuses(NewPostalService(nil, ps.log))
949+ svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
950 svc.Start()
951 ps.blacklisted = false
952
953
954=== modified file 'client/service/service_test.go'
955--- client/service/service_test.go 2014-07-14 19:51:43 +0000
956+++ client/service/service_test.go 2014-08-11 21:30:50 +0000
957@@ -28,6 +28,7 @@
958
959 "launchpad.net/ubuntu-push/bus"
960 testibus "launchpad.net/ubuntu-push/bus/testing"
961+ "launchpad.net/ubuntu-push/click"
962 "launchpad.net/ubuntu-push/logger"
963 "launchpad.net/ubuntu-push/nih"
964 helpers "launchpad.net/ubuntu-push/testing"
965@@ -162,7 +163,7 @@
966 {nil, ErrBadArgCount},
967 {[]interface{}{}, ErrBadArgCount},
968 {[]interface{}{1}, ErrBadArgType},
969- {[]interface{}{"foo"}, ErrBadAppId},
970+ {[]interface{}{"foo"}, click.ErrInvalidAppId},
971 {[]interface{}{"foo", "bar"}, ErrBadArgCount},
972 } {
973 reg, err := new(PushService).register("/bar", s.args, nil)
974
975=== modified file 'debian/changelog'
976--- debian/changelog 2014-08-04 15:38:34 +0000
977+++ debian/changelog 2014-08-11 21:30:50 +0000
978@@ -1,3 +1,25 @@
979+ubuntu-push (0.61) UNRELEASED; urgency=medium
980+
981+ [ Guillermo Gonzalez ]
982+ * Update autopilot tests to work with 0.50, fix setup.sh issues and add new tests for the broadcast notification changes.
983+ * Replace whoopsie with /var/lib/dbus/machine-id to get the device ID.
984+
985+ [ John R. Lenton]
986+ * Support simpler sounds API.
987+ * Support simpler vibrations API.
988+ * Remove Vibration's confusing and redundant Duration attribute.
989+ * Change PostalService's New() to take a setup object.
990+ * goctest.
991+ * Make messaging menu entries show current time instead of epoch for timestamp of 0.
992+ * Tweak the upstart script, start after unity.
993+ * Correctly report invalid app ids, missing apps, and package/app id mismatches as separate errors over dbus.
994+
995+ [Roberto Alsina]
996+ * Check that sound paths don't go up into the tree.
997+ * Initial draft of QML-based doc
998+
999+ -- Roberto Alsina <ralsina@yoga> Mon, 11 Aug 2014 18:23:32 -0300
1000+
1001 ubuntu-push (0.60+14.10.20140804-0ubuntu1) utopic; urgency=medium
1002
1003 [ Guillermo Gonzalez ]
1004
1005=== modified file 'debian/config.json'
1006--- debian/config.json 2014-07-03 13:59:15 +0000
1007+++ debian/config.json 2014-08-11 21:30:50 +0000
1008@@ -12,5 +12,7 @@
1009 "recheck_timeout": "10m",
1010 "connectivity_check_url": "http://start.ubuntu.com/connectivity-check.html",
1011 "connectivity_check_md5": "4589f42e1546aa47ca181e5d949d310b",
1012- "log_level": "debug"
1013+ "log_level": "debug",
1014+ "fallback_vibration": {"pattern": [100, 100], "repeat": 2},
1015+ "fallback_sound": "sounds/ubuntu/notifications/Slick.ogg"
1016 }
1017
1018=== modified file 'debian/ubuntu-push-client.conf'
1019--- debian/ubuntu-push-client.conf 2014-06-02 10:01:13 +0000
1020+++ debian/ubuntu-push-client.conf 2014-08-11 21:30:50 +0000
1021@@ -1,7 +1,9 @@
1022 description "ubuntu push notification client-side daemon"
1023
1024-start on started dbus
1025-stop on stopped dbus
1026+start on started unity8
1027+stop on stopping unity8
1028
1029 exec /usr/lib/ubuntu-push-client/ubuntu-push-client
1030 respawn
1031+
1032+post-stop exec initctl emit untrusted-helper-type-end HELPER_TYPE=push-helper
1033
1034=== added file 'docs/highlevel.txt'
1035--- docs/highlevel.txt 1970-01-01 00:00:00 +0000
1036+++ docs/highlevel.txt 2014-08-11 21:30:50 +0000
1037@@ -0,0 +1,325 @@
1038+Ubuntu Push Client Developer Guide
1039+==================================
1040+
1041+:Version: 0.50+
1042+
1043+Introduction
1044+------------
1045+
1046+This document describes how to use the Ubuntu Push Client service from the point of view of a developer writing
1047+a QML-based application.
1048+
1049+---------
1050+
1051+Let's describe the push system by way of an example.
1052+
1053+Alice has written a chat application called Chatter. Using it, Bob can send messages to Carol and viceversa. Alice has a
1054+web application for it, so the way it works now is that Bob connects to the service, posts a message, and when Carol
1055+connects, she gets it. If Carol leaves the browser window open, it beeps when messages arrive.
1056+
1057+Now Alice wants to create an Ubuntu Touch app for Chatter, so she implements the same architecture using a client that
1058+does the same thing as the web browser. Sadly, since applications on Ubuntu Touch don't run continuously, messages are
1059+only delivered when Carol opens the app, and the user experience suffers.
1060+
1061+Using the Ubuntu Push Server, this problem is alleviated: the Chatter server will deliver the messages to the Ubuntu
1062+Push Server, which in turn will send it in an efficient manner to the Ubuntu Push Client running in Bob and Carol's
1063+devices. The user sees a notification (all without starting the app) and then can launch it if he's interested in
1064+reading messages at that point.
1065+
1066+Since the app is not started and messages are delivered oportunistically, this is both battery and bandwidth-efficient.
1067+
1068+.. figure:: push.svg
1069+
1070+The Ubuntu Push system provides:
1071+
1072+* A push server which receives **push messages** from the app servers, queues them and delivers them efficiently
1073+ to the devices.
1074+* A push client which receives those messages, queues messages to the app and displays notifications to the user
1075+
1076+The full lifecycle of a push message is:
1077+
1078+* Created in a application-specific server
1079+* Sent to the Ubuntu Push server, targeted at a user or user+device pair
1080+* Delivered to one or more Ubuntu devices
1081+* Passed through the application helper for processing
1082+* Notification displayed to the user (via different mechanisms)
1083+* Application Message queued for the app's use
1084+
1085+If the user interacts with the notification, the application is launched and should check its queue for messages
1086+it has to process.
1087+
1088+For the app developer, there are several components needed:
1089+
1090+* A server that sends the **push messages** to the Ubuntu Push server
1091+* Support in the client app for registering with the Ubuntu Push client
1092+* Support in the client app to react to **notifications** displayed to the user and process **application messages**
1093+* A helper program with application-specific knowledge that transforms **push messages** as needed.
1094+
1095+In the following sections, we'll see how to implement all the client side parts. For the application server, see the
1096+`Ubuntu Push Server API section <#ubuntu-push-server-api>`__
1097+
1098+The PushClient Component
1099+------------------------
1100+
1101+Example::
1102+
1103+ import Ubuntu.PushNotifications 0.1
1104+
1105+ PushClient {
1106+ id: pushClient
1107+ Component.onCompleted: {
1108+ newNotifications.connect(messageList.handle_notifications)
1109+ error.connect(messageList.handle_error)
1110+ }
1111+ appId: "com.ubuntu.developer.push.hello_hello"
1112+ }
1113+
1114+Registration: the appId and token properties
1115+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1116+
1117+To register with the push system and start receiving notifications, set the ``appId`` property to your application's APP_ID,
1118+with or without version number. For this to succeed the user **must** have an Ubuntu One account configured in the device.
1119+
1120+The APP_ID is as described in the `ApplicationId documentation <https://wiki.ubuntu.com/AppStore/Interfaces/ApplicationId>`__
1121+except that the version is treated as optional. Therefore both ``com.ubuntu.music_music`` and ``com.ubuntu.music_music_1.3.496``
1122+are valid. Keep in mind that while both versioned and unversioned APP_IDs are valid, they are still different and will affect
1123+which notifications are delivered to the application. Unversioned IDs mean the token will be the same after updates and the application
1124+will receive old notifications, while versioned IDs mean the app needs to explicitly ask to get older messages delivered.
1125+
1126+Setting the same appId more than once has no effect.
1127+
1128+After you are registered, if no error occurs, the PushClient will have a value set in its ``token`` property
1129+which uniquely identifies the user+device combination.
1130+
1131+Receiving Notifications
1132+~~~~~~~~~~~~~~~~~~~~~~~
1133+
1134+When a notification is received by the Push Client, it will be delivered to your application's push helper, and then
1135+placed in your application's mailbox. At that point, the PushClient will emit the ``newNotifications(QStringList)`` signal
1136+containing your messages. You should probably connect to that signal and handle those messages.
1137+
1138+Because of the application's lifecycle, there is no guarantee that it will be running when the signal is emitted. For that
1139+reason, apps should check for pending notifications whenever they are activated or started. To do that, use the
1140+``getNotifications()`` slot. Triggering that slot will fetch notifications and trigger the
1141+``newNotifications(QStringList)`` signal.
1142+
1143+Error Handling
1144+~~~~~~~~~~~~~~
1145+
1146+Whenever PushClient suffers an error, it will emit the ``error(QString)`` signal with the error message.
1147+
1148+Persistent Notification Management
1149+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1150+
1151+Some notifications are persistent, meaning they don't disappear automatically. For those notifications, there is an API that
1152+allows the app to manage them without having to know the underlying details of the platform.
1153+
1154+On each notification there's an optional ``tag`` field, used for this purpose.
1155+
1156+The ``persistent`` property of PushClient contains the list of the tags of notifications with the "persist" element set
1157+to true that are visible to the user right now.
1158+
1159+The ``void clearPersistent(QStringList tags)`` method clears persistent notifications for that app marked by ``tags``.
1160+If no tag is given, match all.
1161+
1162+
1163+The ``count`` property sets the counter in the application's icon to the given value.
1164+
1165+
1166+Application Helpers
1167+-------------------
1168+
1169+The payload delivered to push-client will be passed onto a helper program that can modify it as needed before passing it onto
1170+the postal service (see `Helper Output Format <#helper-output-format>`__).
1171+
1172+The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed
1173+version is placed in ``outfile``.
1174+
1175+This is the simplest possible useful helper, which simply passes the message through unchanged::
1176+
1177+ #!/usr/bin/python3
1178+
1179+ import sys
1180+ f1, f2 = sys.argv[1:3]
1181+ open(f2, "w").write(open(f1).read())
1182+
1183+Helpers need to be added to the click package manifest::
1184+
1185+ {
1186+ "name": "com.ubuntu.developer.ralsina.hello",
1187+ "description": "description of hello",
1188+ "framework": "ubuntu-sdk-14.10-qml-dev2",
1189+ "architecture": "all",
1190+ "title": "hello",
1191+ "hooks": {
1192+ "hello": {
1193+ "apparmor": "hello.json",
1194+ "desktop": "hello.desktop"
1195+ },
1196+ "helloHelper": {
1197+ "apparmor": "helloHelper-apparmor.json",
1198+ "push-helper": "helloHelper.json"
1199+ }
1200+ },
1201+ "version": "0.2",
1202+ "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>"
1203+ }
1204+
1205+Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.
1206+
1207+helloHelper-apparmor.json must contain **only** the push-notification-client policy group::
1208+
1209+ {
1210+ "policy_groups": [
1211+ "push-notification-client"
1212+ ],
1213+ "policy_version": 1.2
1214+ }
1215+
1216+And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally
1217+an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).
1218+If the app_id is not specified, the helper will be used for all apps in the package::
1219+
1220+ {
1221+ "exec": "helloHelper",
1222+ "app_id": "com.ubuntu.developer.ralsina.hello_hello"
1223+ }
1224+
1225+.. note:: For deb packages, helpers should be installed into /usr/lib/ubuntu-push-client/legacy-helpers/ as part of the package.
1226+
1227+Helper Output Format
1228+--------------------
1229+
1230+Helpers output has two parts, the postal message (in the "message" key) and a notification to be presented to the user (in the "notification" key).
1231+
1232+Here's a simple example::
1233+
1234+ {
1235+ "message": "foobar",
1236+ "notification": {
1237+ "tag": "foo",
1238+ "card": {
1239+ "summary": "yes",
1240+ "body": "hello",
1241+ "popup": true,
1242+ "persist": true,
1243+ "timestamp": 1407160197
1244+ }
1245+ "sound": "buzz.mp3",
1246+ "vibrate": {
1247+ "pattern": [200, 100],
1248+ "repeat": 2
1249+ }
1250+ "emblem-counter": {
1251+ "count": 12,
1252+ "visible": true
1253+ }
1254+ }
1255+ }
1256+
1257+The notification can contain a **tag** field, which can later be used by the `persistent notification management API. <#persistent-notification-management>`__
1258+
1259+:message: (optional) A JSON object that is passed as-is to the application via PopAll.
1260+:notification: (optional) Describes the user-facing notifications triggered by this push message.
1261+
1262+The notification can contain a **card**. A card describes a specific notification to be given to the user,
1263+and has the following fields:
1264+
1265+:summary: (required) a title. The card will not be presented if this is missing.
1266+:body: longer text, defaults to empty.
1267+:actions: If empty (the default), a bubble notification is non-clickable.
1268+ If you add a URL, then bubble notifications are clickable and launch that URL. One use for this is using a URL like
1269+ ``appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version`` which will switch to the app or launch
1270+ it if it's not running. See `URLDispatcher <https://wiki.ubuntu.com/URLDispatcher>`__ for more information.
1271+
1272+:icon: An icon relating to the event being notified. Defaults to empty (no icon);
1273+ a secondary icon relating to the application will be shown as well, regardless of this field.
1274+:timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp.
1275+:persist: Whether to show in notification centre; defaults to false
1276+:popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false.
1277+
1278+.. note:: Keep in mind that the precise way in which each field is presented to the user depends on factors such as
1279+ whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user
1280+ has on their device.
1281+
1282+The notification can contain a **sound** field. This is either a boolean (play a predetermined sound) or the path to a sound file. The user can disable it, so don't rely on it exclusively.
1283+Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b)
1284+standard xdg dirs.
1285+
1286+The notification can contain a **vibrate** field, causing haptic feedback, which can be either a boolean (if true, vibrate a predetermined way) or an object that has the following content:
1287+
1288+:pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds).
1289+:repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1).
1290+
1291+The notification can contain a **emblem-counter** field, with the following content:
1292+
1293+:count: a number to be displayed over the application's icon in the launcher.
1294+:visible: set to true to show the counter, or false to hide it.
1295+
1296+.. note:: Unlike other notifications, emblem-counter needs to be cleaned by the app itself.
1297+ Please see `the persistent notification management section. <#persistent-notification-management>`__
1298+
1299+.. FIXME crosslink to hello example app on each method
1300+
1301+Security
1302+~~~~~~~~
1303+
1304+To use the push API, applications need to request permission in their security profile, using something like this::
1305+
1306+ {
1307+ "policy_groups": [
1308+ "networking",
1309+ "push-notification-client"
1310+ ],
1311+ "policy_version": 1.2
1312+ }
1313+
1314+
1315+Ubuntu Push Server API
1316+----------------------
1317+
1318+The Ubuntu Push server is located at https://push.ubuntu.com and has a single endpoint: ``/notify``.
1319+To notify a user, your application has to do a POST with ``Content-type: application/json``.
1320+
1321+Here is an example of the POST body using all the fields::
1322+
1323+ {
1324+ "appid": "com.ubuntu.music_music",
1325+ "expire_on": "2014-10-08T14:48:00.000Z",
1326+ "token": "LeA4tRQG9hhEkuhngdouoA==",
1327+ "clear_pending": true,
1328+ "replace_tag": "tagname",
1329+ "data": {
1330+ "message": "foobar",
1331+ "notification": {
1332+ "card": {
1333+ "summary": "yes",
1334+ "body": "hello",
1335+ "popup": true,
1336+ "persist": true,
1337+ "timestamp": 1407160197
1338+ }
1339+ "sound": "buzz.mp3",
1340+ "tag": "foo",
1341+ "vibrate": {
1342+ "pattern": [200, 100],
1343+ "repeat": 2
1344+ }
1345+ "emblem-counter": {
1346+ "count": 12,
1347+ "visible": true
1348+ }
1349+ }
1350+ }
1351+ }
1352+
1353+
1354+:appid: ID of the application that will receive the notification, as described in the client side documentation.
1355+:expire_on: Expiration date/time for this message, in `ISO8601 Extendend format <http://en.wikipedia.org/wiki/ISO_8601>`__
1356+:token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
1357+:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
1358+:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
1359+:data: A JSON object.
1360+
1361+In this example, data is `what a helper would output <#helper-output-format>`__ but that's not necessarily the case.
1362+The content of the data field will be passed to the helper application which **has** to produce output in that format.
1363
1364=== modified file 'docs/lowlevel.txt'
1365--- docs/lowlevel.txt 2014-07-29 17:14:22 +0000
1366+++ docs/lowlevel.txt 2014-08-11 21:30:50 +0000
1367@@ -73,7 +73,7 @@
1368 Each Ubuntu Touch package has to use a separate object path for security reasons, that's why the object path includes QUOTED_PKGNAME.
1369 For example, in the case of the music application, the package name is ``com.ubuntu.music`` and QUOTED_PKGNAME is com_2eubuntu_2emusic.
1370 Everything that is not a letter or digit has to be quoted as _XX where XX are the hex digits of the character. In practice,
1371-this means replacing "." with "_2e" and "-" with "_2f"
1372+this means replacing "." with "_2e" and "-" with "_2d"
1373
1374 .. note:: For applications that are not installed as part of click packages, the QUOTED_PKGNAME is "_" and the APP_ID when required is
1375 _PACKAGENAME.
1376@@ -296,12 +296,12 @@
1377 "summary": "yes",
1378 "body": "hello",
1379 "popup": true,
1380- "persist": true
1381+ "persist": true,
1382+ "timestamp": 1407160197
1383 }
1384 "sound": "buzz.mp3",
1385 "vibrate": {
1386 "pattern": [200, 100],
1387- "duration": 200,
1388 "repeat": 2
1389 }
1390 "emblem-counter": {
1391@@ -328,7 +328,7 @@
1392
1393 :icon: An icon relating to the event being notified. Defaults to empty (no icon);
1394 a secondary icon relating to the application will be shown as well, regardless of this field.
1395-:timestamp: Seconds since the unix epoch, only used for persist (for now)
1396+:timestamp: Seconds since the unix epoch, only used for persist (for now). If zero or unset, defaults to current timestamp.
1397 :persist: Whether to show in notification centre; defaults to false
1398 :popup: Whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false.
1399
1400@@ -336,14 +336,13 @@
1401 whether it's shown as a bubble or in the notification centre, or even the version of Ubuntu Touch the user
1402 has on their device.
1403
1404-The notification can contain a **sound** field. This is the path to a sound file. The user can disable it, so don't rely on it exclusively.
1405-Defaults to empty (no sound). This is a relative path, and will be looked up in (a) the application's .local/share/<pkgname>, and (b)
1406+The notification can contain a **sound** field. This is either a boolean (play a predetermined sound) or the path to a sound file. The user can disable it, so don't rely on it exclusively.
1407+Defaults to empty (no sound). The path is relative, and will be looked up in (a) the application's .local/share/<pkgname>, and (b)
1408 standard xdg dirs.
1409
1410-The notification can contain a **vibrate** field, causing haptic feedback, that has the following content:
1411+The notification can contain a **vibrate** field, causing haptic feedback, which can be either a boolean (if true, vibrate a predetermined way) or an object that has the following content:
1412
1413-:pattern: a list of integers describing a vibration pattern.
1414-:duration: duration in milliseconds. Is equivalent to setting pattern to [duration], and overrides pattern.
1415+:pattern: a list of integers describing a vibration pattern (duration of alternating vibration/no vibration times, in milliseconds).
1416 :repeat: number of times the pattern has to be repeated (defaults to 1, 0 is the same as 1).
1417
1418 The notification can contain a **emblem-counter** field, with the following content:
1419@@ -391,13 +390,13 @@
1420 "summary": "yes",
1421 "body": "hello",
1422 "popup": true,
1423- "persist": true
1424+ "persist": true,
1425+ "timestamp": 1407160197
1426 }
1427 "sound": "buzz.mp3",
1428 "tag": "foo",
1429 "vibrate": {
1430- "duration": 200,
1431- "pattern": (200, 100),
1432+ "pattern": [200, 100],
1433 "repeat": 2
1434 }
1435 "emblem-counter": {
1436
1437=== added directory 'identifier'
1438=== added file 'identifier/identifier.go'
1439--- identifier/identifier.go 1970-01-01 00:00:00 +0000
1440+++ identifier/identifier.go 2014-08-11 21:30:50 +0000
1441@@ -0,0 +1,59 @@
1442+/*
1443+ Copyright 2013-2014 Canonical Ltd.
1444+
1445+ This program is free software: you can redistribute it and/or modify it
1446+ under the terms of the GNU General Public License version 3, as published
1447+ by the Free Software Foundation.
1448+
1449+ This program is distributed in the hope that it will be useful, but
1450+ WITHOUT ANY WARRANTY; without even the implied warranties of
1451+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1452+ PURPOSE. See the GNU General Public License for more details.
1453+
1454+ You should have received a copy of the GNU General Public License along
1455+ with this program. If not, see <http://www.gnu.org/licenses/>.
1456+*/
1457+
1458+// Package identifier is the source of an anonymous and stable
1459+// system id (from /var/lib/dbus/machine-id) used by the Ubuntu
1460+// push notifications service.
1461+package identifier
1462+
1463+import (
1464+ "fmt"
1465+ "io/ioutil"
1466+)
1467+
1468+var machineIdPath = "/var/lib/dbus/machine-id"
1469+
1470+// an Id knows how to generate itself, and how to stringify itself.
1471+type Id interface {
1472+ String() string
1473+}
1474+
1475+// Identifier is the default Id implementation.
1476+type Identifier struct {
1477+ value string
1478+}
1479+
1480+func readMachineId() (string, error) {
1481+ value, err := ioutil.ReadFile(machineIdPath)
1482+ if err != nil {
1483+ return "", err
1484+ }
1485+ return string(value)[:len(value)-1], nil
1486+}
1487+
1488+// New creates an Identifier
1489+func New() (Id, error) {
1490+ value, err := readMachineId()
1491+ if err != nil {
1492+ return &Identifier{value: ""}, fmt.Errorf("Failed to read the machine id: %s", err)
1493+ }
1494+ return &Identifier{value: value}, nil
1495+}
1496+
1497+// String returns the system identifier as a string.
1498+func (id *Identifier) String() string {
1499+ return id.value
1500+}
1501
1502=== added file 'identifier/identifier_test.go'
1503--- identifier/identifier_test.go 1970-01-01 00:00:00 +0000
1504+++ identifier/identifier_test.go 2014-08-11 21:30:50 +0000
1505@@ -0,0 +1,54 @@
1506+/*
1507+ Copyright 2013-2014 Canonical Ltd.
1508+
1509+ This program is free software: you can redistribute it and/or modify it
1510+ under the terms of the GNU General Public License version 3, as published
1511+ by the Free Software Foundation.
1512+
1513+ This program is distributed in the hope that it will be useful, but
1514+ WITHOUT ANY WARRANTY; without even the implied warranties of
1515+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1516+ PURPOSE. See the GNU General Public License for more details.
1517+
1518+ You should have received a copy of the GNU General Public License along
1519+ with this program. If not, see <http://www.gnu.org/licenses/>.
1520+*/
1521+
1522+package identifier
1523+
1524+import (
1525+ . "launchpad.net/gocheck"
1526+ "testing"
1527+)
1528+
1529+// hook up gocheck
1530+func Test(t *testing.T) { TestingT(t) }
1531+
1532+type IdentifierSuite struct{}
1533+
1534+var _ = Suite(&IdentifierSuite{})
1535+
1536+// TestNew checks that New does not fail, and returns a
1537+// 32-byte string.
1538+func (s *IdentifierSuite) TestNew(c *C) {
1539+ id, err := New()
1540+ c.Check(err, IsNil)
1541+ c.Check(id.String(), HasLen, 32)
1542+}
1543+
1544+// TestNewFail checks that when we can't read the machine-id
1545+// file the error is propagated
1546+func (s *IdentifierSuite) TestNewFail(c *C) {
1547+ // replace the machine-id file path
1548+ machineIdPath = "/var/lib/dbus/no-such-file"
1549+ id, err := New()
1550+ c.Check(err, NotNil)
1551+ c.Check(err.Error(), Equals, "Failed to read the machine id: open /var/lib/dbus/no-such-file: no such file or directory")
1552+ c.Check(id.String(), HasLen, 0)
1553+}
1554+
1555+// TestIdentifierInterface checks that Identifier implements Id.
1556+func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
1557+ id, _ := New()
1558+ _ = []Id{id}
1559+}
1560
1561=== added directory 'identifier/testing'
1562=== added file 'identifier/testing/testing.go'
1563--- identifier/testing/testing.go 1970-01-01 00:00:00 +0000
1564+++ identifier/testing/testing.go 2014-08-11 21:30:50 +0000
1565@@ -0,0 +1,58 @@
1566+/*
1567+ Copyright 2013-2014 Canonical Ltd.
1568+
1569+ This program is free software: you can redistribute it and/or modify it
1570+ under the terms of the GNU General Public License version 3, as published
1571+ by the Free Software Foundation.
1572+
1573+ This program is distributed in the hope that it will be useful, but
1574+ WITHOUT ANY WARRANTY; without even the implied warranties of
1575+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1576+ PURPOSE. See the GNU General Public License for more details.
1577+
1578+ You should have received a copy of the GNU General Public License along
1579+ with this program. If not, see <http://www.gnu.org/licenses/>.
1580+*/
1581+
1582+// Package testing implements a couple of Ids that are useful
1583+// for testing things that use identifier.
1584+package testing
1585+
1586+// SettableIdentifier is an Id that lets you set the value of the identifier.
1587+//
1588+// By default the identifier's value is "<Settable>", so it's visible
1589+// if you're misusing it.
1590+type SettableIdentifier struct {
1591+ value string
1592+}
1593+
1594+// Settable is the constructor for SettableIdentifier.
1595+func Settable() *SettableIdentifier {
1596+ return &SettableIdentifier{"<Settable>"}
1597+}
1598+
1599+// Set is the method you use to set the identifier.
1600+func (sid *SettableIdentifier) Set(value string) {
1601+ sid.value = value
1602+}
1603+
1604+// String returns the string you set.
1605+func (sid *SettableIdentifier) String() string {
1606+ return sid.value
1607+}
1608+
1609+// FailingIdentifier is an Id that always fails to generate.
1610+type FailingIdentifier struct{}
1611+
1612+// Failing is the constructor for FailingIdentifier.
1613+func Failing() *FailingIdentifier {
1614+ return &FailingIdentifier{}
1615+}
1616+
1617+// String returns "<Failing>".
1618+//
1619+// The purpose of this is to make it easy to spot if you're using it
1620+// by accident.
1621+func (*FailingIdentifier) String() string {
1622+ return "<Failing>"
1623+}
1624
1625=== added file 'identifier/testing/testing_test.go'
1626--- identifier/testing/testing_test.go 1970-01-01 00:00:00 +0000
1627+++ identifier/testing/testing_test.go 2014-08-11 21:30:50 +0000
1628@@ -0,0 +1,57 @@
1629+/*
1630+ Copyright 2013-2014 Canonical Ltd.
1631+
1632+ This program is free software: you can redistribute it and/or modify it
1633+ under the terms of the GNU General Public License version 3, as published
1634+ by the Free Software Foundation.
1635+
1636+ This program is distributed in the hope that it will be useful, but
1637+ WITHOUT ANY WARRANTY; without even the implied warranties of
1638+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1639+ PURPOSE. See the GNU General Public License for more details.
1640+
1641+ You should have received a copy of the GNU General Public License along
1642+ with this program. If not, see <http://www.gnu.org/licenses/>.
1643+*/
1644+
1645+package testing
1646+
1647+import (
1648+ identifier ".."
1649+ . "launchpad.net/gocheck"
1650+ "testing"
1651+)
1652+
1653+// hook up gocheck
1654+func Test(t *testing.T) { TestingT(t) }
1655+
1656+type IdentifierSuite struct{}
1657+
1658+var _ = Suite(&IdentifierSuite{})
1659+
1660+// TestSettableDefaultValueVisible tests that SettableIdentifier's default
1661+// value is notable.
1662+func (s *IdentifierSuite) TestSettableDefaultValueVisible(c *C) {
1663+ id := Settable()
1664+ c.Check(id.String(), Equals, "<Settable>")
1665+}
1666+
1667+// TestSettableSets tests that SettableIdentifier is settable.
1668+func (s *IdentifierSuite) TestSettableSets(c *C) {
1669+ id := Settable()
1670+ id.Set("hello")
1671+ c.Check(id.String(), Equals, "hello")
1672+}
1673+
1674+// TestFailingStringNotEmpty tests that FailingIdentifier still has a
1675+// non-empty string.
1676+func (s *IdentifierSuite) TestFailingStringNotEmpty(c *C) {
1677+ id := Failing()
1678+ c.Check(id.String(), Equals, "<Failing>")
1679+}
1680+
1681+// TestIdentifierInterface tests that FailingIdentifier and
1682+// SettableIdentifier implement Id.
1683+func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
1684+ _ = []identifier.Id{Failing(), Settable()}
1685+}
1686
1687=== modified file 'launch_helper/helper_output.go'
1688--- launch_helper/helper_output.go 2014-07-22 14:08:25 +0000
1689+++ launch_helper/helper_output.go 2014-08-11 21:30:50 +0000
1690@@ -18,6 +18,7 @@
1691
1692 import (
1693 "encoding/json"
1694+ "time"
1695
1696 "launchpad.net/ubuntu-push/click"
1697 )
1698@@ -25,13 +26,13 @@
1699 // a Card is the usual “visual” presentation of a notification, used
1700 // for bubbles and the notification centre (neé messaging menu)
1701 type Card struct {
1702- Summary string `json:"summary"` // required for the card to be presented
1703- Body string `json:"body"` // defaults to empty
1704- Actions []string `json:"actions"` // if empty (default), bubble is non-clickable. More entries change it to be clickable and (for bubbles) snap-decisions.
1705- Icon string `json:"icon"` // an icon relating to the event being notified. Defaults to empty (no icon); a secondary icon relating to the application will be shown as well, irrespectively.
1706- Timestamp int `json:"timestamp"` // seconds since epoch, only used for persist (for now)
1707- Persist bool `json:"persist"` // whether to show in notification centre; defaults to false
1708- Popup bool `json:"popup"` // whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false.
1709+ Summary string `json:"summary"` // required for the card to be presented
1710+ Body string `json:"body"` // defaults to empty
1711+ Actions []string `json:"actions"` // if empty (default), bubble is non-clickable. More entries change it to be clickable and (for bubbles) snap-decisions.
1712+ Icon string `json:"icon"` // an icon relating to the event being notified. Defaults to empty (no icon); a secondary icon relating to the application will be shown as well, irrespectively.
1713+ RawTimestamp int `json:"timestamp"` // seconds since epoch, only used for persist (for now). Timestamp() returns this if non-zero, current timestamp otherwise.
1714+ Persist bool `json:"persist"` // whether to show in notification centre; defaults to false
1715+ Popup bool `json:"popup"` // whether to show in a bubble. Users can disable this, and can easily miss them, so don't rely on it exclusively. Defaults to false.
1716 }
1717
1718 // an EmblemCounter puts a number on an emblem on an app's icon in the launcher
1719@@ -43,18 +44,17 @@
1720 // a Vibration generates a vibration in the form of a Pattern set in
1721 // duration a pattern of on off states, repeated a number of times
1722 type Vibration struct {
1723- Duration uint32 `json:"duration"` // if Duration is present and not 0, it's like a Pattern of [Duration]; otherwise, Pattern is used.
1724- Pattern []uint32 `json:"pattern"`
1725- Repeat uint32 `json:"repeat"` // defaults to 1. A value of zero is ignored (so it's like 1).
1726+ Pattern []uint32 `json:"pattern"`
1727+ Repeat uint32 `json:"repeat"` // defaults to 1. A value of zero is ignored (so it's like 1).
1728 }
1729
1730 // a Notification can be any of the above
1731 type Notification struct {
1732- Card *Card `json:"card"` // defaults to nil (no card)
1733- Sound string `json:"sound"` // a sound file. Users can disable this, so don't rely on it exclusively. Defaults to empty (no sound).
1734- Vibrate *Vibration `json:"vibrate"` // users can disable this, blah blah. Defaults to null (no vibration)
1735- EmblemCounter *EmblemCounter `json:"emblem-counter"` // puts a counter on an emblem in the launcher. Defaults to nil (no change to emblem counter).
1736- Tag string `json:"tag,omitempty"` // tag used for Clear/ListPersistent.
1737+ Card *Card `json:"card"` // defaults to nil (no card)
1738+ RawSound json.RawMessage `json:"sound"` // a boolean, or the relative path to a sound file. Users can disable this, so don't rely on it exclusively. Defaults to empty (no sound).
1739+ RawVibration json.RawMessage `json:"vibrate"` // users can disable this, blah blah. Can be Vibration, or boolean. Defaults to null (no vibration)
1740+ EmblemCounter *EmblemCounter `json:"emblem-counter"` // puts a counter on an emblem in the launcher. Defaults to nil (no change to emblem counter).
1741+ Tag string `json:"tag,omitempty"` // tag used for Clear/ListPersistent.
1742 }
1743
1744 // HelperOutput is the expected output of a helper
1745@@ -76,3 +76,58 @@
1746 NotificationId string
1747 Payload json.RawMessage
1748 }
1749+
1750+// Timestamp() returns RawTimestamp if non-zero. If it's zero, returns
1751+// the current time as second since epoch.
1752+func (card *Card) Timestamp() int64 {
1753+ if card.RawTimestamp == 0 {
1754+ return time.Now().Unix()
1755+ } else {
1756+ return int64(card.RawTimestamp)
1757+ }
1758+}
1759+
1760+func (notification *Notification) Vibration(fallback *Vibration) *Vibration {
1761+ var b bool
1762+ var vib *Vibration
1763+
1764+ if notification.RawVibration == nil {
1765+ return nil
1766+ }
1767+ if json.Unmarshal(notification.RawVibration, &b) == nil {
1768+ if !b {
1769+ return nil
1770+ } else {
1771+ return fallback
1772+ }
1773+ }
1774+ if json.Unmarshal(notification.RawVibration, &vib) != nil {
1775+ return nil
1776+ }
1777+ if len(vib.Pattern) == 0 {
1778+ return nil
1779+ }
1780+
1781+ return vib
1782+}
1783+
1784+func (notification *Notification) Sound(fallback string) string {
1785+ var b bool
1786+ var s string
1787+
1788+ if notification.RawSound == nil {
1789+ return ""
1790+ }
1791+ if json.Unmarshal(notification.RawSound, &b) == nil {
1792+ if !b {
1793+ return ""
1794+ } else {
1795+ return fallback
1796+ }
1797+ }
1798+ if json.Unmarshal(notification.RawSound, &s) != nil {
1799+ return ""
1800+ }
1801+
1802+ return s
1803+}
1804
1805=== added file 'launch_helper/helper_output_test.go'
1806--- launch_helper/helper_output_test.go 1970-01-01 00:00:00 +0000
1807+++ launch_helper/helper_output_test.go 2014-08-11 21:30:50 +0000
1808@@ -0,0 +1,96 @@
1809+/*
1810+ Copyright 2014 Canonical Ltd.
1811+
1812+ This program is free software: you can redistribute it and/or modify it
1813+ under the terms of the GNU General Public License version 3, as published
1814+ by the Free Software Foundation.
1815+
1816+ This program is distributed in the hope that it will be useful, but
1817+ WITHOUT ANY WARRANTY; without even the implied warranties of
1818+ MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1819+ PURPOSE. See the GNU General Public License for more details.
1820+
1821+ You should have received a copy of the GNU General Public License along
1822+ with this program. If not, see <http://www.gnu.org/licenses/>.
1823+*/
1824+
1825+package launch_helper
1826+
1827+import (
1828+ "encoding/json"
1829+ "time"
1830+
1831+ . "launchpad.net/gocheck"
1832+)
1833+
1834+type outSuite struct{}
1835+
1836+var _ = Suite(&outSuite{})
1837+
1838+func (*outSuite) TestCardGetTimestamp(c *C) {
1839+ t := time.Now().Add(-2 * time.Second)
1840+ var card Card
1841+ err := json.Unmarshal([]byte(`{"timestamp": 12}`), &card)
1842+ c.Assert(err, IsNil)
1843+ c.Check(card, DeepEquals, Card{RawTimestamp: 12})
1844+ c.Check(time.Unix((&Card{}).Timestamp(), 0).After(t), Equals, true)
1845+ c.Check((&Card{RawTimestamp: 42}).Timestamp(), Equals, int64(42))
1846+}
1847+
1848+func (*outSuite) TestBadVibeBegetsNilVibe(c *C) {
1849+ fbck := &Vibration{Repeat: 2}
1850+ for _, s := range []string{
1851+ `{}`,
1852+ `{"vibrate": "foo"}`,
1853+ `{"vibrate": {}}`,
1854+ `{"vibrate": false}`, // not bad, but rather pointless
1855+ `{"vibrate": {"repeat": 2}}`, // no pattern
1856+ `{"vibrate": {"repeat": "foo"}}`,
1857+ `{"vibrate": {"pattern": "foo"}}`,
1858+ `{"vibrate": {"pattern": ["foo"]}}`,
1859+ `{"vibrate": {"pattern": null}}`,
1860+ `{"vibrate": {"pattern": [-1]}}`,
1861+ `{"vibrate": {"pattern": [1], "repeat": -1}}`,
1862+ } {
1863+ var notif *Notification
1864+ err := json.Unmarshal([]byte(s), &notif)
1865+ c.Assert(err, IsNil)
1866+ c.Assert(notif, NotNil)
1867+ c.Check(notif.Vibration(fbck), IsNil, Commentf("not nil Vibration() for: %s", s))
1868+ c.Check(notif.Vibration(fbck), IsNil, Commentf("not nil second call to Vibration() for: %s", s))
1869+ }
1870+}
1871+
1872+func (*outSuite) TestGoodVibe(c *C) {
1873+ var notif *Notification
1874+ err := json.Unmarshal([]byte(`{"vibrate": {"pattern": [1,2,3], "repeat": 2}}`), &notif)
1875+ c.Assert(err, IsNil)
1876+ c.Assert(notif, NotNil)
1877+ c.Check(notif.Vibration(nil), DeepEquals, &Vibration{Pattern: []uint32{1, 2, 3}, Repeat: 2})
1878+}
1879+
1880+func (*outSuite) TestGoodSimpleVibe(c *C) {
1881+ var notif *Notification
1882+ fallback := &Vibration{Pattern: []uint32{100, 100}, Repeat: 3}
1883+ err := json.Unmarshal([]byte(`{"vibrate": true}`), &notif)
1884+ c.Assert(err, IsNil)
1885+ c.Assert(notif, NotNil)
1886+ c.Check(notif.Vibration(fallback), Equals, fallback)
1887+}
1888+
1889+func (*outSuite) TestBadSoundBegetsNoSound(c *C) {
1890+ c.Check((&Notification{RawSound: json.RawMessage("foo")}).Sound("x"), Equals, "")
1891+}
1892+
1893+func (*outSuite) TestNilSoundBegetsNoSound(c *C) {
1894+ c.Check((&Notification{RawSound: nil}).Sound("x"), Equals, "")
1895+}
1896+
1897+func (*outSuite) TestGoodSound(c *C) {
1898+ c.Check((&Notification{RawSound: json.RawMessage(`"foo"`)}).Sound("x"), Equals, "foo")
1899+}
1900+
1901+func (*outSuite) TestGoodSimpleSound(c *C) {
1902+ c.Check((&Notification{RawSound: json.RawMessage(`true`)}).Sound("x"), Equals, "x")
1903+ c.Check((&Notification{RawSound: json.RawMessage(`false`)}).Sound("x"), Equals, "")
1904+}
1905
1906=== modified file 'launch_helper/helper_test.go'
1907--- launch_helper/helper_test.go 2014-07-22 14:08:25 +0000
1908+++ launch_helper/helper_test.go 2014-08-11 21:30:50 +0000
1909@@ -43,7 +43,7 @@
1910 }
1911
1912 func (s *runnerSuite) TestTrivialPoolWorks(c *C) {
1913- notif := &Notification{Sound: "42", Tag: "foo"}
1914+ notif := &Notification{RawSound: json.RawMessage(`"42"`), Tag: "foo"}
1915
1916 triv := NewTrivialHelperPool(s.testlog)
1917 ch := triv.Start()
1918
1919=== modified file 'launch_helper/kindpool_test.go'
1920--- launch_helper/kindpool_test.go 2014-07-30 09:26:25 +0000
1921+++ launch_helper/kindpool_test.go 2014-08-11 21:30:50 +0000
1922@@ -17,6 +17,7 @@
1923 package launch_helper
1924
1925 import (
1926+ "encoding/json"
1927 "fmt"
1928 "io/ioutil"
1929 "os"
1930@@ -285,7 +286,7 @@
1931
1932 res := takeNext(ch, c)
1933
1934- expected := HelperOutput{Notification: &Notification{Sound: "hello", Tag: "a-tag"}}
1935+ expected := HelperOutput{Notification: &Notification{RawSound: json.RawMessage(`"hello"`), Tag: "a-tag"}}
1936 c.Check(res.HelperOutput, DeepEquals, expected)
1937 c.Check(pool.hmap, HasLen, 0)
1938 }
1939
1940=== modified file 'messaging/cmessaging/cmessaging.go'
1941--- messaging/cmessaging/cmessaging.go 2014-07-24 16:25:55 +0000
1942+++ messaging/cmessaging/cmessaging.go 2014-08-11 21:30:50 +0000
1943@@ -84,7 +84,7 @@
1944 body := gchar(card.Body)
1945 defer gfree(body)
1946
1947- timestamp := (C.gint64)(int64(card.Timestamp) * 1000000)
1948+ timestamp := (C.gint64)(card.Timestamp() * 1000000)
1949
1950 C.add_notification(desktop_id, notification_id, icon_path, summary, body, timestamp, nil, (C.gpointer)(payload))
1951 }
1952
1953=== added file 'scripts/connect-many.py'
1954--- scripts/connect-many.py 1970-01-01 00:00:00 +0000
1955+++ scripts/connect-many.py 2014-08-11 21:30:50 +0000
1956@@ -0,0 +1,29 @@
1957+#!/usr/bin/python3
1958+import sys
1959+import resource
1960+import socket
1961+import ssl
1962+import time
1963+
1964+host, port = sys.argv[1].split(":")
1965+addr = (host, int(port))
1966+soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
1967+# reset soft == hard
1968+resource.setrlimit(resource.RLIMIT_NOFILE, (hard, hard))
1969+
1970+conns = []
1971+t0 = time.time()
1972+try:
1973+ for i in range(soft+100):
1974+ s=socket.socket()
1975+ w = ssl.wrap_socket(s)
1976+ w.settimeout(1)
1977+ w.connect(addr)
1978+ conns.append(w)
1979+ w.send(b"x")
1980+except Exception as e:
1981+ print("%s|%d|%s" % (e, len(conns), time.time()-t0))
1982+ sys.exit(0)
1983+
1984+print("UNTROUBLED|%d" % len(conns))
1985+sys.exit(1)
1986
1987=== added file 'scripts/goctest'
1988--- scripts/goctest 1970-01-01 00:00:00 +0000
1989+++ scripts/goctest 2014-08-11 21:30:50 +0000
1990@@ -0,0 +1,46 @@
1991+#!/usr/bin/python3
1992+# -*- python -*-
1993+# (c) 2014 John Lenton
1994+# MIT licensed.
1995+# from https://github.com/chipaca/goctest
1996+
1997+import re
1998+import signal
1999+import subprocess
2000+import sys
2001+
2002+ok_rx = re.compile(rb'^(PASS:?|ok\s+)')
2003+fail_rx = re.compile(rb'^(FAIL:?|OOPS:?)')
2004+panic_rx = re.compile(rb'^(PANIC:?|panic:?|\.\.\. Panic:?)')
2005+log_rx = re.compile(rb'^\[LOG\]|^\?\s+')
2006+
2007+class bcolors:
2008+ OK = b'\033[38;5;34m'
2009+ FAIL = b'\033[38;5;196m'
2010+ PANIC = b'\033[38;5;226m\033[48;5;88m'
2011+ OTHER = b'\033[38;5;241m'
2012+ WARNING = b'\033[38;5;226m'
2013+ ENDC = b'\033[0m'
2014+
2015+signal.signal(signal.SIGINT, lambda *_: None)
2016+
2017+if sys.stdout.isatty():
2018+ with subprocess.Popen(["go", "test"] + sys.argv[1:],
2019+ bufsize=0,
2020+ stderr=subprocess.STDOUT,
2021+ stdout=subprocess.PIPE) as proc:
2022+ for line in proc.stdout:
2023+ if panic_rx.search(line) is not None:
2024+ line = panic_rx.sub(bcolors.PANIC + rb'\1' + bcolors.ENDC, line)
2025+ elif fail_rx.search(line) is not None:
2026+ line = fail_rx.sub(bcolors.FAIL + rb'\1' + bcolors.ENDC, line)
2027+ elif ok_rx.search(line) is not None:
2028+ line = ok_rx.sub(bcolors.OK + rb'\1' + bcolors.ENDC, line)
2029+ elif log_rx.search(line) is not None:
2030+ line = bcolors.OTHER + line + bcolors.ENDC
2031+
2032+ sys.stdout.write(line.decode("utf-8"))
2033+ sys.stdout.flush()
2034+ sys.exit(proc.wait())
2035+else:
2036+ sys.exit(subprocess.call(["go", "test"] + sys.argv[1:]))
2037
2038=== modified file 'server/listener/listener_test.go'
2039--- server/listener/listener_test.go 2014-03-06 19:21:44 +0000
2040+++ server/listener/listener_test.go 2014-08-11 21:30:50 +0000
2041@@ -20,9 +20,12 @@
2042 "crypto/tls"
2043 "crypto/x509"
2044 "net"
2045+ "os/exec"
2046+ "regexp"
2047 "syscall"
2048 "testing"
2049 "time"
2050+ "unicode"
2051
2052 . "launchpad.net/gocheck"
2053
2054@@ -37,7 +40,7 @@
2055
2056 var _ = Suite(&listenerSuite{})
2057
2058-const NofileMax = 500
2059+const NofileMax = 20
2060
2061 func (s *listenerSuite) SetUpSuite(*C) {
2062 // make it easier to get a too many open files error
2063@@ -111,13 +114,19 @@
2064
2065 func testSession(conn net.Conn) error {
2066 defer conn.Close()
2067- conn.SetDeadline(time.Now().Add(2 * time.Second))
2068+ conn.SetDeadline(time.Now().Add(10 * time.Second))
2069 var buf [1]byte
2070- _, err := conn.Read(buf[:])
2071- if err != nil {
2072- return err
2073+ for {
2074+ _, err := conn.Read(buf[:])
2075+ if err != nil {
2076+ return err
2077+ }
2078+ // 1|2... send digit back
2079+ if unicode.IsDigit(rune(buf[0])) {
2080+ break
2081+ }
2082 }
2083- _, err = conn.Write(buf[:])
2084+ _, err := conn.Write(buf[:])
2085 return err
2086 }
2087
2088@@ -165,6 +174,18 @@
2089 c.Check(s.testlog.Captured(), Equals, "")
2090 }
2091
2092+// waitForLogs waits for the logs captured in s.testlog to match reStr.
2093+func (s *listenerSuite) waitForLogs(c *C, reStr string) {
2094+ rx := regexp.MustCompile("^" + reStr + "$")
2095+ for i := 0; i < 100; i++ {
2096+ if rx.MatchString(s.testlog.Captured()) {
2097+ break
2098+ }
2099+ time.Sleep(20 * time.Millisecond)
2100+ }
2101+ c.Check(s.testlog.Captured(), Matches, reStr)
2102+}
2103+
2104 func (s *listenerSuite) TestDeviceAcceptLoopTemporaryError(c *C) {
2105 // ENFILE is not the temp network error we want to handle this way
2106 // but is relatively easy to generate in a controlled way
2107@@ -177,20 +198,11 @@
2108 errCh <- lst.AcceptLoop(testSession, s.testlog)
2109 }()
2110 listenerAddr := lst.Addr().String()
2111- conns := make([]net.Conn, 0, NofileMax)
2112- for i := 0; i < NofileMax; i++ {
2113- var conn1 net.Conn
2114- conn1, err = net.Dial("tcp", listenerAddr)
2115- if err != nil {
2116- break
2117- }
2118- defer conn1.Close()
2119- conns = append(conns, conn1)
2120- }
2121- c.Assert(err, ErrorMatches, "*.too many open.*")
2122- for _, conn := range conns {
2123- conn.Close()
2124- }
2125+ connectMany := helpers.ScriptAbsPath("connect-many.py")
2126+ cmd := exec.Command(connectMany, listenerAddr)
2127+ res, err := cmd.Output()
2128+ c.Assert(err, IsNil)
2129+ c.Assert(string(res), Matches, "(?s).*timed out.*")
2130 conn2, err := testTlsDial(c, listenerAddr)
2131 c.Assert(err, IsNil)
2132 defer conn2.Close()
2133@@ -198,7 +210,7 @@
2134 testReadByte(c, conn2, '2')
2135 lst.Close()
2136 c.Check(<-errCh, ErrorMatches, ".*use of closed.*")
2137- c.Check(s.testlog.Captured(), Matches, ".*device listener:.*accept.*too many open.*-- retrying\n")
2138+ s.waitForLogs(c, "(?ms).*device listener:.*accept.*too many open.*-- retrying")
2139 }
2140
2141 func (s *listenerSuite) TestDeviceAcceptLoopPanic(c *C) {
2142@@ -217,7 +229,7 @@
2143 c.Assert(err, Not(IsNil))
2144 lst.Close()
2145 c.Check(<-errCh, ErrorMatches, ".*use of closed.*")
2146- c.Check(s.testlog.Captured(), Matches, "(?s)ERROR\\(PANIC\\) terminating device connection on: session crash:.*AcceptLoop.*")
2147+ s.waitForLogs(c, "(?s)ERROR\\(PANIC\\) terminating device connection on: session crash:.*AcceptLoop.*")
2148 }
2149
2150 func (s *listenerSuite) TestForeignListener(c *C) {
2151
2152=== modified file 'sounds/sounds.go'
2153--- sounds/sounds.go 2014-07-25 10:25:59 +0000
2154+++ sounds/sounds.go 2014-08-11 21:30:50 +0000
2155@@ -17,9 +17,11 @@
2156 package sounds
2157
2158 import (
2159+ "errors"
2160 "os"
2161 "os/exec"
2162 "path/filepath"
2163+ "strings"
2164
2165 "launchpad.net/go-xdg/v0"
2166
2167@@ -31,12 +33,19 @@
2168 type Sound struct {
2169 player string
2170 log logger.Logger
2171+ fallback string
2172 dataDirs func() []string
2173 dataFind func(string) (string, error)
2174 }
2175
2176-func New(log logger.Logger) *Sound {
2177- return &Sound{player: "paplay", log: log, dataDirs: xdg.Data.Dirs, dataFind: xdg.Data.Find}
2178+func New(log logger.Logger, fallback string) *Sound {
2179+ return &Sound{
2180+ player: "paplay",
2181+ log: log,
2182+ fallback: fallback,
2183+ dataDirs: xdg.Data.Dirs,
2184+ dataFind: xdg.Data.Find,
2185+ }
2186 }
2187
2188 func (snd *Sound) Present(app *click.AppId, nid string, notification *launch_helper.Notification) bool {
2189@@ -44,13 +53,14 @@
2190 panic("please check notification is not nil before calling present")
2191 }
2192
2193- if notification.Sound == "" {
2194- snd.log.Debugf("[%s] notification has no Sound: %#v", nid, notification.Sound)
2195+ sound := notification.Sound(snd.fallback)
2196+ if sound == "" {
2197+ snd.log.Debugf("[%s] notification has no Sound: %#v", nid, sound)
2198 return false
2199 }
2200- absPath := snd.findSoundFile(app, nid, notification.Sound)
2201+ absPath := snd.findSoundFile(app, nid, sound)
2202 if absPath == "" {
2203- snd.log.Debugf("[%s] unable to find sound %s", nid, notification.Sound)
2204+ snd.log.Debugf("[%s] unable to find sound %s", nid, sound)
2205 return false
2206 }
2207 snd.log.Debugf("[%s] playing sound %s using %s", nid, absPath, snd.player)
2208@@ -69,9 +79,23 @@
2209 return true
2210 }
2211
2212+// Removes all cruft from path, ensures it's a "forward" path.
2213+func (snd *Sound) cleanPath(path string) (string, error) {
2214+ cleaned := filepath.Clean(path)
2215+ if strings.Contains(cleaned, "../") {
2216+ return "", errors.New("Path escaping xdg attempt")
2217+ }
2218+ return cleaned, nil
2219+}
2220+
2221 func (snd *Sound) findSoundFile(app *click.AppId, nid string, sound string) string {
2222 // XXX also support legacy appIds?
2223 // first, check package-specific
2224+ sound, err := snd.cleanPath(sound)
2225+ if err != nil {
2226+ // bad boy
2227+ return ""
2228+ }
2229 absPath, err := snd.dataFind(filepath.Join(app.Package, sound))
2230 if err == nil {
2231 // ffffound
2232
2233=== modified file 'sounds/sounds_test.go'
2234--- sounds/sounds_test.go 2014-07-25 10:25:59 +0000
2235+++ sounds/sounds_test.go 2014-08-11 21:30:50 +0000
2236@@ -17,6 +17,7 @@
2237 package sounds
2238
2239 import (
2240+ "encoding/json"
2241 "errors"
2242 "os"
2243 "path"
2244@@ -45,9 +46,10 @@
2245 }
2246
2247 func (ss *soundsSuite) TestNew(c *C) {
2248- s := New(ss.log)
2249+ s := New(ss.log, "foo")
2250 c.Check(s.log, Equals, ss.log)
2251 c.Check(s.player, Equals, "paplay")
2252+ c.Check(s.fallback, Equals, "foo")
2253 }
2254
2255 func (ss *soundsSuite) TestPresent(c *C) {
2256@@ -57,10 +59,22 @@
2257 }
2258
2259 c.Check(s.Present(ss.app, "",
2260- &launch_helper.Notification{Sound: "hello"}), Equals, true)
2261+ &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, true)
2262 c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/hello using echo`)
2263 }
2264
2265+func (ss *soundsSuite) TestPresentSimple(c *C) {
2266+ s := &Sound{
2267+ player: "echo", log: ss.log,
2268+ dataFind: func(s string) (string, error) { return s, nil },
2269+ fallback: "fallback",
2270+ }
2271+
2272+ c.Check(s.Present(ss.app, "",
2273+ &launch_helper.Notification{RawSound: json.RawMessage(`true`)}), Equals, true)
2274+ c.Check(ss.log.Captured(), Matches, `(?sm).* playing sound com.example.test/fallback using echo`)
2275+}
2276+
2277 func (ss *soundsSuite) TestPresentFails(c *C) {
2278 s := &Sound{
2279 player: "/",
2280@@ -74,10 +88,10 @@
2281 // no Sound
2282 c.Check(s.Present(ss.app, "", &launch_helper.Notification{}), Equals, false)
2283 // bad player
2284- c.Check(s.Present(ss.app, "", &launch_helper.Notification{Sound: "hello"}), Equals, false)
2285+ c.Check(s.Present(ss.app, "", &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, false)
2286 s.player = "echo"
2287 // no file found
2288- c.Check(s.Present(ss.app, "", &launch_helper.Notification{Sound: "hello"}), Equals, false)
2289+ c.Check(s.Present(ss.app, "", &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, false)
2290
2291 // and now, just to prove it would've worked,
2292
2293@@ -86,5 +100,31 @@
2294 c.Assert(err, IsNil)
2295 f.Close()
2296 s.dataDirs = func() []string { return []string{"", d} }
2297- c.Check(s.Present(ss.app, "", &launch_helper.Notification{Sound: "hello"}), Equals, true)
2298+ c.Check(s.Present(ss.app, "", &launch_helper.Notification{RawSound: json.RawMessage(`"hello"`)}), Equals, true)
2299+}
2300+
2301+func (ss *soundsSuite) TestBadPathFails(c *C) {
2302+ s := &Sound{
2303+ player: "/",
2304+ log: ss.log,
2305+ dataFind: func(string) (string, error) { return "", errors.New("nope") },
2306+ dataDirs: func() []string { return []string{""} },
2307+ }
2308+
2309+ sound, err := s.cleanPath("../../foo")
2310+ c.Check(err, NotNil)
2311+ c.Check(sound, Equals, "")
2312+}
2313+
2314+func (ss *soundsSuite) TestGoodPathSucceeds(c *C) {
2315+ s := &Sound{
2316+ player: "/",
2317+ log: ss.log,
2318+ dataFind: func(string) (string, error) { return "", errors.New("nope") },
2319+ dataDirs: func() []string { return []string{""} },
2320+ }
2321+
2322+ sound, err := s.cleanPath("foo/../bar")
2323+ c.Check(err, IsNil)
2324+ c.Check(sound, Equals, "bar")
2325 }
2326
2327=== modified file 'tests/autopilot/push_notifications/tests/__init__.py'
2328--- tests/autopilot/push_notifications/tests/__init__.py 2014-07-09 23:26:45 +0000
2329+++ tests/autopilot/push_notifications/tests/__init__.py 2014-08-11 21:30:50 +0000
2330@@ -26,6 +26,7 @@
2331 import evdev
2332
2333 from autopilot.introspection import dbus
2334+from autopilot.exceptions import StateNotFoundError
2335 from push_notifications import config as push_config
2336 import push_notifications.helpers.push_notifications_helper as push_helper
2337 from testtools.matchers import Equals
2338@@ -238,6 +239,22 @@
2339 """
2340 self.validate_and_dismiss_notification_dialog(message, secondary_icon=False)
2341
2342+ def validate_and_tap_notification_dialog(self, message,
2343+ secondary_icon=True):
2344+ """
2345+ Validate a notification dialog is displayed and dismiss it
2346+ :param message: expected message displayed in summary
2347+ """
2348+ # get the dialog
2349+ dialog, props = self.get_notification_dialog()
2350+ # validate dialog
2351+ self.assert_notification_dialog(
2352+ props, summary=message, secondary_icon=secondary_icon)
2353+ # tap the dialog
2354+ self.touch.tap_object(dialog)
2355+ # check the dialog is no longer displayed
2356+ self.validate_notification_not_displayed(wait=False)
2357+
2358 def wait_until_dialog_dismissed(self, dialog):
2359 """Wait for the dialog to dismiss automatically"""
2360 dialog_disappeared = False
2361@@ -270,7 +287,7 @@
2362 self.wait_until_dialog_dismissed(dialog)
2363
2364 # unicast messages
2365- def send_unicast_notification(self, icon="messages-app",
2366+ def send_unicast_notification(self, icon="",
2367 body="A unicast message", summary="Look!",
2368 persist=False, popup=True, actions=[], emblem_counter={}):
2369 """Build and send a push unicast message.
2370@@ -309,3 +326,36 @@
2371 indicator_page = self.main_window.open_indicator_page(
2372 "indicator-messages")
2373 return indicator_page
2374+
2375+ def validate_mmu_notification(self, body_text, title_text):
2376+ # get the mmu notification and check the body and title.
2377+ # swipe down and show the incomming page
2378+ messaging = self.get_messaging_menu()
2379+ # get the notification and check the body and title.
2380+ menuItem0 = messaging.select_single('QQuickLoader',
2381+ objectName='menuItem0')
2382+ hmh = menuItem0.select_single('HeroMessageHeader')
2383+ body = hmh.select_single("Label", objectName='body')
2384+ self.assertEqual(body.text, body_text)
2385+ title = hmh.select_single("Label", objectName='title')
2386+ self.assertEqual(title.text, title_text)
2387+ self.clear_mmu(ignore_missing=False)
2388+
2389+ def clear_mmu(self, ignore_missing=True):
2390+ # get the mmu notification and check the body and title.
2391+ messaging = self.get_messaging_menu()
2392+ # clear all notifications
2393+ try:
2394+ clear_all = messaging.select_single(
2395+ 'ButtonMenu', objectName='indicator.remove-all')
2396+ except StateNotFoundError:
2397+ if not ignore_missing:
2398+ raise
2399+ return
2400+ emptyLabel = messaging.select_single('Label',
2401+ objectName='emptyLabel')
2402+ self.assertFalse(emptyLabel.visible)
2403+ self.touch.tap_object(clear_all)
2404+ emptyLabel = messaging.select_single('Label',
2405+ objectName='emptyLabel')
2406+ self.assertTrue(emptyLabel.visible)
2407
2408=== modified file 'tests/autopilot/push_notifications/tests/test_broadcast_notifications.py'
2409--- tests/autopilot/push_notifications/tests/test_broadcast_notifications.py 2014-07-09 18:37:25 +0000
2410+++ tests/autopilot/push_notifications/tests/test_broadcast_notifications.py 2014-08-11 21:30:50 +0000
2411@@ -21,15 +21,19 @@
2412
2413 import time
2414
2415+from autopilot import platform
2416+from testtools import skipIf
2417+
2418 from push_notifications.tests import PushNotificationTestBase
2419
2420
2421+DEFAULT_DISPLAY_MESSAGE = "There's an updated system image."
2422+MMU_BODY = "Tap to open the system updater."
2423+MMU_TITLE = DEFAULT_DISPLAY_MESSAGE
2424+
2425+
2426 class TestPushClientBroadcast(PushNotificationTestBase):
2427- """
2428- Test cases for broadcast push notifications
2429- """
2430-
2431- DEFAULT_DISPLAY_MESSAGE = 'There\'s an updated system image.'
2432+ """Test cases for broadcast push notifications"""
2433
2434 def test_broadcast_push_notification_screen_off(self):
2435 """
2436@@ -45,8 +49,12 @@
2437 time.sleep(2)
2438 # Turn display on
2439 self.press_power_button()
2440- self.validate_and_dismiss_broadcast_notification_dialog(
2441- self.DEFAULT_DISPLAY_MESSAGE)
2442+ # Assumes greeter starts in locked state
2443+ self.unlock_greeter()
2444+ # check the bubble is there
2445+ self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE)
2446+ # clear the mmu
2447+ self.clear_mmu()
2448
2449 def test_broadcast_push_notification_locked_greeter(self):
2450 """
2451@@ -54,10 +62,13 @@
2452 to the client and validate that a notification message is displayed
2453 whist the greeter screen is displayed
2454 """
2455+ self.send_push_broadcast_message()
2456+ # check the bubble is there
2457+ self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE)
2458 # Assumes greeter starts in locked state
2459- self.send_push_broadcast_message()
2460- self.validate_and_dismiss_broadcast_notification_dialog(
2461- self.DEFAULT_DISPLAY_MESSAGE)
2462+ self.unlock_greeter()
2463+ # clear the mmu
2464+ self.clear_mmu()
2465
2466 def test_broadcast_push_notification(self):
2467 """
2468@@ -67,8 +78,42 @@
2469 # Assumes greeter starts in locked state
2470 self.unlock_greeter()
2471 self.send_push_broadcast_message()
2472- self.validate_and_dismiss_broadcast_notification_dialog(
2473- self.DEFAULT_DISPLAY_MESSAGE)
2474+ # check the bubble is there
2475+ self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE)
2476+ # clear the mmu
2477+ self.clear_mmu()
2478+
2479+ @skipIf(platform.model() == 'X86 Emulator',
2480+ "Test not working in the emulator")
2481+ def test_broadcast_push_notification_is_persistent(self):
2482+ """
2483+ Positive test case to send a valid broadcast push notification
2484+ to the client and validate that a notification message is displayed
2485+ and includes a persitent mmu notification.
2486+ """
2487+ # Assumes greeter starts in locked state
2488+ self.unlock_greeter()
2489+ self.send_push_broadcast_message()
2490+ # check the bubble is there
2491+ self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE)
2492+ # check the mmu notification is there
2493+ self.validate_mmu_notification(MMU_BODY, MMU_TITLE)
2494+
2495+ def test_broadcast_push_notification_click_bubble_clears_mmu(self):
2496+ """
2497+ Positive test case to send a valid broadcast push notification
2498+ to the client and validate that a notification message is displayed
2499+ """
2500+ # Assumes greeter starts in locked state
2501+ self.unlock_greeter()
2502+ self.send_push_broadcast_message()
2503+ # check the bubble is there
2504+ self.validate_and_tap_notification_dialog(DEFAULT_DISPLAY_MESSAGE)
2505+ # swipe down and show the incomming page
2506+ messaging = self.get_messaging_menu()
2507+ emptyLabel = messaging.select_single('Label',
2508+ objectName='emptyLabel')
2509+ self.assertTrue(emptyLabel.visible)
2510
2511 def test_broadcast_push_notification_on_connect(self):
2512 """
2513@@ -81,8 +126,10 @@
2514 self.push_client_controller.stop_push_client()
2515 self.send_push_broadcast_message()
2516 self.push_client_controller.start_push_client()
2517- self.validate_and_dismiss_broadcast_notification_dialog(
2518- self.DEFAULT_DISPLAY_MESSAGE)
2519+ # check the bubble is there
2520+ self.validate_and_dismiss_notification_dialog(DEFAULT_DISPLAY_MESSAGE)
2521+ # clear the mmu
2522+ self.clear_mmu()
2523
2524 def test_expired_broadcast_push_notification(self):
2525 """
2526
2527=== modified file 'tests/autopilot/push_notifications/tests/test_unicast_notifications.py'
2528--- tests/autopilot/push_notifications/tests/test_unicast_notifications.py 2014-07-10 01:21:45 +0000
2529+++ tests/autopilot/push_notifications/tests/test_unicast_notifications.py 2014-08-11 21:30:50 +0000
2530@@ -21,6 +21,9 @@
2531
2532 import os
2533
2534+from autopilot import platform
2535+from testtools import skipIf
2536+
2537 from push_notifications.tests import PushNotificationTestBase
2538
2539
2540@@ -30,21 +33,24 @@
2541 DEFAULT_DISPLAY_MESSAGE = 'Look!'
2542
2543 scenarios = [('click_app_with_version',
2544- dict(app_name="com.ubuntu.calculator_calculator",
2545+ dict(app_name="com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter",
2546 appid=None, path=None,
2547 desktop_dir="~/.local/share/applications/",
2548- launcher_idx=4)),
2549+ icon="twitter",
2550+ launcher_idx=5)),
2551 ('click_app',
2552- dict(app_name="com.ubuntu.calculator_calculator",
2553- appid="com.ubuntu.calculator_calculator",
2554- path="com_2eubuntu_2ecalculator",
2555+ dict(app_name="com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter",
2556+ appid="com.ubuntu.developer.webapps.webapp-twitter_webapp-twitter",
2557+ path="com_2eubuntu_2edeveloper_2ewebapps_2ewebapp_2dtwitter",
2558 desktop_dir="~/.local/share/applications/",
2559- launcher_idx=4)),
2560+ icon="twitter",
2561+ launcher_idx=5)),
2562 ('legacy_app',
2563 dict(app_name="messaging-app",
2564 appid="_messaging-app",
2565 path="_",
2566 desktop_dir="/usr/share/applications/",
2567+ icon="messages-app",
2568 launcher_idx=1))]
2569
2570 def setUp(self):
2571@@ -63,10 +69,13 @@
2572 fname.endswith(".desktop"):
2573 # remove .desktop extension, only need the name.
2574 appid = os.path.splitext(fname)[0]
2575- path = appid.split("_")[0].replace(".", "_2e")
2576+ path = appid.split("_")[0]
2577+ path = path.replace(".", "_2e").replace("-", "_2d")
2578 return appid, path
2579 return self.appid, self.path
2580
2581+ @skipIf(platform.model() == 'X86 Emulator',
2582+ "Test not working in the emulator")
2583 def test_unicast_push_notification_persistent(self):
2584 """Send a persistent unicast push notification.
2585
2586@@ -76,17 +85,9 @@
2587 # Assumes greeter starts in locked state
2588 self.unlock_greeter()
2589 # send message
2590- self.send_unicast_notification(persist=True)
2591- # swipe down and show the incomming page
2592- messaging = self.get_messaging_menu()
2593- # get the notification and check the body and title.
2594- menuItem0 = messaging.select_single('QQuickLoader',
2595- objectName='menuItem0')
2596- hmh = menuItem0.select_single('HeroMessageHeader')
2597- body = hmh.select_single("Label", objectName='body')
2598- self.assertEqual(body.text, 'A unicast message')
2599- title = hmh.select_single("Label", objectName='title')
2600- self.assertEqual(title.text, 'Look!')
2601+ self.send_unicast_notification(persist=True, popup=False,
2602+ icon=self.icon)
2603+ self.validate_mmu_notification("A unicast message", "Look!")
2604
2605 def get_running_app_launcher_icon(self):
2606 launcher = self.main_window.get_launcher()
2607@@ -118,6 +119,7 @@
2608 # send message, only showing emblem counter
2609 emblem_counter = {'count': 42, 'visible': True}
2610 self.send_unicast_notification(persist=False, popup=False,
2611+ icon=self.icon,
2612 emblem_counter=emblem_counter)
2613 # show the dash and check the emblem count.
2614 self.main_window.show_dash_swiping()
2615@@ -131,14 +133,15 @@
2616 The notification should be displayed on top of the greeter.
2617 """
2618 # Assumes greeter starts in locked state
2619- self.send_unicast_notification(summary="Locked greeter")
2620+ self.send_unicast_notification(summary="Locked greeter",
2621+ icon=self.icon)
2622 self.validate_and_dismiss_notification_dialog("Locked greeter")
2623
2624 def test_unicast_push_notification(self):
2625 """Send a push notificationn and validate it's displayed."""
2626 # Assumes greeter starts in locked state
2627 self.unlock_greeter()
2628- self.send_unicast_notification()
2629+ self.send_unicast_notification(icon=self.icon)
2630 self.validate_and_dismiss_notification_dialog(
2631 self.DEFAULT_DISPLAY_MESSAGE)
2632
2633@@ -151,7 +154,7 @@
2634 # Assumes greeter starts in locked state
2635 self.unlock_greeter()
2636 self.push_client_controller.stop_push_client()
2637- self.send_unicast_notification()
2638+ self.send_unicast_notification(icon=self.icon)
2639 self.push_client_controller.start_push_client()
2640 self.validate_and_dismiss_notification_dialog(
2641 self.DEFAULT_DISPLAY_MESSAGE)
2642
2643=== modified file 'tests/autopilot/run.sh'
2644--- tests/autopilot/run.sh 2014-07-10 13:25:57 +0000
2645+++ tests/autopilot/run.sh 2014-08-11 21:30:50 +0000
2646@@ -8,15 +8,17 @@
2647 usage: $0 options
2648
2649 OPTIONS:
2650+ -t fqn of the tests to run (module, class or method)
2651 -d adb device_id
2652 -v run tests in verbose mode
2653 EOF
2654 }
2655
2656-while getopts "d:v" opt; do
2657+while getopts "t:d:v" opt; do
2658 case $opt in
2659 d) DEVICE_ID=$OPTARG;;
2660 v) VERBOSITY="-v";;
2661+ t) TESTS=$OPTARG;;
2662 *) usage
2663 exit 1
2664 ;;
2665@@ -24,7 +26,9 @@
2666 done
2667
2668
2669+VERBOSITY=${VERBOSITY:-""}
2670+TESTS=${TESTS:-"push_notifications"}
2671 DEVICE_ID=${DEVICE_ID:-"emulator-5554"}
2672 BASE_DIR="ubuntu-push/src/launchpad.net"
2673 BRANCH_DIR="$BASE_DIR/ubuntu-push"
2674-adb -s ${DEVICE_ID} shell "su - phablet bash -c 'cd ${BRANCH_DIR}/tests/autopilot/; /sbin/initctl stop unity8; autopilot3 run ${VERBOSITY} push_notifications'"
2675+adb -s ${DEVICE_ID} shell "su - phablet bash -c 'cd ${BRANCH_DIR}/tests/autopilot/; /sbin/initctl stop unity8; autopilot3 run ${VERBOSITY} ${TESTS}'"
2676
2677=== modified file 'tests/autopilot/setup.sh'
2678--- tests/autopilot/setup.sh 2014-07-10 20:54:12 +0000
2679+++ tests/autopilot/setup.sh 2014-08-11 21:30:50 +0000
2680@@ -15,11 +15,11 @@
2681 EOF
2682 }
2683
2684-while getopts "H:b:c:u" opt; do
2685+while getopts "H:b:d:u" opt; do
2686 case $opt in
2687 H) PUSH_SERVER=$OPTARG;;
2688 b) BRANCH_URL=$OPTARG;;
2689- c) DEVICE_ID=$OPTARG;;
2690+ d) DEVICE_ID=$OPTARG;;
2691 u) APT_UPDATE="1";;
2692 *) usage
2693 exit 1
2694@@ -54,18 +54,28 @@
2695 adb -s ${DEVICE_ID} shell "touch autopilot-deps.ok"
2696 fi
2697 # fetch the code
2698-BASE_DIR="ubuntu-push/src/launchpad.net"
2699+BASE_DIR="/home/phablet/ubuntu-push/src/launchpad.net"
2700 BRANCH_DIR="$BASE_DIR/ubuntu-push"
2701 BRANCH_OK=$(adb -s ${DEVICE_ID} shell "su - phablet bash -c '[ ! -d "${BRANCH_DIR}/tests/autopilot" ] && echo 1 || echo 0'")
2702-if [[ "${BRANCH_OK:0:1}" == 1 ]]
2703+if [[ "${BRANCH_OK:0:1}" == 1 ]] || [[ "${BRANCH_URL}" != "lp:ubuntu-push/automatic" ]]
2704 then
2705+ adb -s ${DEVICE_ID} shell "su - phablet bash -c 'rm -Rf ${BRANCH_DIR}'"
2706 echo "fetching code."
2707 adb -s ${DEVICE_ID} shell "su - phablet bash -c 'mkdir -p ${BASE_DIR}'"
2708 adb -s ${DEVICE_ID} shell "su - phablet bash -c 'bzr branch ${BRANCH_URL} ${BRANCH_DIR}'"
2709 fi
2710-adb -s ${DEVICE_ID} shell "su - phablet bash -c 'sed -i 's/192.168.1.3/${PUSH_SERVER}/' ${BRANCH_DIR}/tests/autopilot/push_notifications/config/push.conf'"
2711+adb -s ${DEVICE_ID} shell "su - phablet bash -c \"sed -i 's/addr =.*/addr = ${PUSH_SERVER}/' ${BRANCH_DIR}/tests/autopilot/push_notifications/config/push.conf\""
2712+
2713+# copy the trivial-helper.sh as the heper for the messaging-app (used in the tests)
2714+HELPER_OK=$(adb -s ${DEVICE_ID} shell "[ ! -f /usr/lib/ubuntu-push-client/legacy-helpers/messaging-app ] && echo 1 || echo 0")
2715+if [[ "${HELPER_OK:0:1}" == 1 ]]
2716+then
2717+ adb -s ${DEVICE_ID} shell "cp ${BRANCH_DIR}/scripts/trivial-helper.sh /usr/lib/ubuntu-push-client/legacy-helpers/messaging-app"
2718+fi
2719
2720 # change the local/dev server config, listen in all interfaces
2721 sed -i 's/127.0.0.1/0.0.0.0/g' ${ROOT_DIR}/sampleconfigs/dev.json
2722 # and start it
2723 cd ${ROOT_DIR}; make run-server-dev
2724+# remove the trivial helper for the messaging-app
2725+adb -s ${DEVICE_ID} shell "rm /usr/lib/ubuntu-push-client/legacy-helpers/messaging-app"
2726
2727=== removed directory 'whoopsie'
2728=== removed file 'whoopsie/doc.go'
2729--- whoopsie/doc.go 2014-02-21 16:17:28 +0000
2730+++ whoopsie/doc.go 1970-01-01 00:00:00 +0000
2731@@ -1,18 +0,0 @@
2732-/*
2733- Copyright 2013-2014 Canonical Ltd.
2734-
2735- This program is free software: you can redistribute it and/or modify it
2736- under the terms of the GNU General Public License version 3, as published
2737- by the Free Software Foundation.
2738-
2739- This program is distributed in the hope that it will be useful, but
2740- WITHOUT ANY WARRANTY; without even the implied warranties of
2741- MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2742- PURPOSE. See the GNU General Public License for more details.
2743-
2744- You should have received a copy of the GNU General Public License along
2745- with this program. If not, see <http://www.gnu.org/licenses/>.
2746-*/
2747-
2748-// Package whoopsie wraps libwhoopsie.
2749-package whoopsie
2750
2751=== removed directory 'whoopsie/identifier'
2752=== removed file 'whoopsie/identifier/identifier.go'
2753--- whoopsie/identifier/identifier.go 2014-07-03 10:51:36 +0000
2754+++ whoopsie/identifier/identifier.go 1970-01-01 00:00:00 +0000
2755@@ -1,78 +0,0 @@
2756-/*
2757- Copyright 2013-2014 Canonical Ltd.
2758-
2759- This program is free software: you can redistribute it and/or modify it
2760- under the terms of the GNU General Public License version 3, as published
2761- by the Free Software Foundation.
2762-
2763- This program is distributed in the hope that it will be useful, but
2764- WITHOUT ANY WARRANTY; without even the implied warranties of
2765- MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2766- PURPOSE. See the GNU General Public License for more details.
2767-
2768- You should have received a copy of the GNU General Public License along
2769- with this program. If not, see <http://www.gnu.org/licenses/>.
2770-*/
2771-
2772-// Package identifier wraps libwhoopsie, and is thus the
2773-// source of an anonymous and stable system id used by the Ubuntu
2774-// error tracker and the Ubuntu push notifications service.
2775-package identifier
2776-
2777-/*
2778-#cgo pkg-config: libwhoopsie
2779-#include <glib.h>
2780-#include <libwhoopsie/identifier.h>
2781-*/
2782-import "C"
2783-import "unsafe"
2784-import "errors"
2785-import "time"
2786-
2787-// an Id knows how to generate itself, and how to stringify itself.
2788-type Id interface {
2789- Generate() error
2790- String() string
2791-}
2792-
2793-// Identifier is the default Id implementation.
2794-type Identifier struct {
2795- value string
2796- generator func(**C.char, **C.GError)
2797-}
2798-
2799-func generator(csp **C.char, errp **C.GError) {
2800- C.whoopsie_identifier_generate(csp, errp)
2801-}
2802-
2803-// New creates an Identifier, but does not call Generate() on it.
2804-func New() Id {
2805- return &Identifier{generator: generator}
2806-}
2807-
2808-// Generate makes the Identifier create the identifier itself.
2809-func (id *Identifier) Generate() error {
2810- var gerr *C.GError
2811- var cs *C.char
2812- defer C.g_free((C.gpointer)(unsafe.Pointer(cs)))
2813-
2814- for i := 0; i < 200; i++ {
2815- id.generator(&cs, &gerr)
2816-
2817- if gerr == nil && cs != nil {
2818- goto Success
2819- }
2820- C.g_clear_error(&gerr)
2821- time.Sleep(600 * time.Millisecond)
2822- }
2823- return errors.New("whoopsie_identifier_generate still bad after 2m; giving up")
2824-
2825-Success:
2826- id.value = C.GoString(cs)
2827- return nil
2828-}
2829-
2830-// String returns the system identifier as a string.
2831-func (id *Identifier) String() string {
2832- return id.value
2833-}
2834
2835=== removed file 'whoopsie/identifier/identifier_test.go'
2836--- whoopsie/identifier/identifier_test.go 2014-05-02 10:42:16 +0000
2837+++ whoopsie/identifier/identifier_test.go 1970-01-01 00:00:00 +0000
2838@@ -1,58 +0,0 @@
2839-/*
2840- Copyright 2013-2014 Canonical Ltd.
2841-
2842- This program is free software: you can redistribute it and/or modify it
2843- under the terms of the GNU General Public License version 3, as published
2844- by the Free Software Foundation.
2845-
2846- This program is distributed in the hope that it will be useful, but
2847- WITHOUT ANY WARRANTY; without even the implied warranties of
2848- MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2849- PURPOSE. See the GNU General Public License for more details.
2850-
2851- You should have received a copy of the GNU General Public License along
2852- with this program. If not, see <http://www.gnu.org/licenses/>.
2853-*/
2854-
2855-package identifier
2856-
2857-import (
2858- . "launchpad.net/gocheck"
2859- "testing"
2860-)
2861-
2862-// hook up gocheck
2863-func Test(t *testing.T) { TestingT(t) }
2864-
2865-type IdentifierSuite struct{}
2866-
2867-var _ = Suite(&IdentifierSuite{})
2868-
2869-// TestGenerate checks that Generate() does not fail, and returns a
2870-// 128-byte string.
2871-func (s *IdentifierSuite) TestGenerate(c *C) {
2872- id := New()
2873-
2874- c.Check(id.Generate(), Equals, nil)
2875- c.Check(id.String(), HasLen, 128)
2876-}
2877-
2878-// TestIdentifierInterface checks that Identifier implements Id.
2879-func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
2880- _ = []Id{New()}
2881-}
2882-
2883-// TestFailure checks that Identifier survives whoopsie shenanigans
2884-func (s *IdentifierSuite) TestIdentifierSurvivesShenanigans(c *C) {
2885- count := 0
2886- // using _Ctype* as a workaround for gocheck also having a C
2887- gen := func(csp **_Ctype_char, errp **_Ctype_GError) {
2888- count++
2889- if count > 3 {
2890- generator(csp, errp)
2891- }
2892- }
2893- id := &Identifier{generator: gen}
2894- id.Generate()
2895- c.Check(id.String(), HasLen, 128)
2896-}
2897
2898=== removed directory 'whoopsie/identifier/testing'
2899=== removed file 'whoopsie/identifier/testing/testing.go'
2900--- whoopsie/identifier/testing/testing.go 2014-02-21 16:17:28 +0000
2901+++ whoopsie/identifier/testing/testing.go 1970-01-01 00:00:00 +0000
2902@@ -1,70 +0,0 @@
2903-/*
2904- Copyright 2013-2014 Canonical Ltd.
2905-
2906- This program is free software: you can redistribute it and/or modify it
2907- under the terms of the GNU General Public License version 3, as published
2908- by the Free Software Foundation.
2909-
2910- This program is distributed in the hope that it will be useful, but
2911- WITHOUT ANY WARRANTY; without even the implied warranties of
2912- MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2913- PURPOSE. See the GNU General Public License for more details.
2914-
2915- You should have received a copy of the GNU General Public License along
2916- with this program. If not, see <http://www.gnu.org/licenses/>.
2917-*/
2918-
2919-// Package testing implements a couple of Ids that are useful
2920-// for testing things that use whoopsie/identifier.
2921-package testing
2922-
2923-import "errors"
2924-
2925-// SettableIdentifier is an Id that lets you set the value of the identifier.
2926-//
2927-// By default the identifier's value is "<Settable>", so it's visible
2928-// if you're misusing it.
2929-type SettableIdentifier struct {
2930- value string
2931-}
2932-
2933-// Settable is the constructor for SettableIdentifier.
2934-func Settable() *SettableIdentifier {
2935- return &SettableIdentifier{"<Settable>"}
2936-}
2937-
2938-// Set is the method you use to set the identifier.
2939-func (sid *SettableIdentifier) Set(value string) {
2940- sid.value = value
2941-}
2942-
2943-// Generate does nothing.
2944-func (sid *SettableIdentifier) Generate() error {
2945- return nil
2946-}
2947-
2948-// String returns the string you set.
2949-func (sid *SettableIdentifier) String() string {
2950- return sid.value
2951-}
2952-
2953-// FailingIdentifier is an Id that always fails to generate.
2954-type FailingIdentifier struct{}
2955-
2956-// Failing is the constructor for FailingIdentifier.
2957-func Failing() *FailingIdentifier {
2958- return &FailingIdentifier{}
2959-}
2960-
2961-// Generate fails with an ubiquitous error.
2962-func (*FailingIdentifier) Generate() error {
2963- return errors.New("lp0 on fire")
2964-}
2965-
2966-// String returns "<Failing>".
2967-//
2968-// The purpose of this is to make it easy to spot if you're using it
2969-// by accident.
2970-func (*FailingIdentifier) String() string {
2971- return "<Failing>"
2972-}
2973
2974=== removed file 'whoopsie/identifier/testing/testing_test.go'
2975--- whoopsie/identifier/testing/testing_test.go 2014-02-21 16:17:28 +0000
2976+++ whoopsie/identifier/testing/testing_test.go 1970-01-01 00:00:00 +0000
2977@@ -1,70 +0,0 @@
2978-/*
2979- Copyright 2013-2014 Canonical Ltd.
2980-
2981- This program is free software: you can redistribute it and/or modify it
2982- under the terms of the GNU General Public License version 3, as published
2983- by the Free Software Foundation.
2984-
2985- This program is distributed in the hope that it will be useful, but
2986- WITHOUT ANY WARRANTY; without even the implied warranties of
2987- MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2988- PURPOSE. See the GNU General Public License for more details.
2989-
2990- You should have received a copy of the GNU General Public License along
2991- with this program. If not, see <http://www.gnu.org/licenses/>.
2992-*/
2993-
2994-package testing
2995-
2996-import (
2997- identifier ".."
2998- . "launchpad.net/gocheck"
2999- "testing"
3000-)
3001-
3002-// hook up gocheck
3003-func Test(t *testing.T) { TestingT(t) }
3004-
3005-type IdentifierSuite struct{}
3006-
3007-var _ = Suite(&IdentifierSuite{})
3008-
3009-// TestSettableDefaultValueVisible tests that SettableIdentifier's default
3010-// value is notable.
3011-func (s *IdentifierSuite) TestSettableDefaultValueVisible(c *C) {
3012- id := Settable()
3013- c.Check(id.String(), Equals, "<Settable>")
3014-}
3015-
3016-// TestSettableSets tests that SettableIdentifier is settable.
3017-func (s *IdentifierSuite) TestSettableSets(c *C) {
3018- id := Settable()
3019- id.Set("hello")
3020- c.Check(id.String(), Equals, "hello")
3021-}
3022-
3023-// TestSettableGenerateDoesNotFail tests that SettableIdentifier's Generate
3024-// does not fail.
3025-func (s *IdentifierSuite) TestSettableGenerateDoesNotFail(c *C) {
3026- id := Settable()
3027- c.Check(id.Generate(), Equals, nil)
3028-}
3029-
3030-// TestFailingFails tests that FailingIdentifier fails.
3031-func (s *IdentifierSuite) TestFailingFails(c *C) {
3032- id := Failing()
3033- c.Check(id.Generate(), Not(Equals), nil)
3034-}
3035-
3036-// TestFailingStringNotEmpty tests that FailingIdentifier still has a
3037-// non-empty string.
3038-func (s *IdentifierSuite) TestFailingStringNotEmpty(c *C) {
3039- id := Failing()
3040- c.Check(id.String(), Equals, "<Failing>")
3041-}
3042-
3043-// TestIdentifierInterface tests that FailingIdentifier and
3044-// SettableIdentifier implement Id.
3045-func (s *IdentifierSuite) TestIdentifierInterface(c *C) {
3046- _ = []identifier.Id{Failing(), Settable()}
3047-}

Subscribers

People subscribed via source and target branches