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

Proposed by John Lenton
Status: Merged
Approved by: John Lenton
Approved revision: no longer in the source branch.
Merged at revision: 119
Proposed branch: lp:~chipaca/ubuntu-push/merge-automatic
Merge into: lp:ubuntu-push
Diff against target: 3155 lines (+1310/-582)
44 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 (+25/-10)
client/client_test.go (+38/-8)
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/control (+1/-1)
debian/ubuntu-push-client.conf (+4/-2)
debian/ubuntu-push-client.install (+1/-1)
docs/highlevel.txt (+325/-0)
docs/lowlevel.txt (+11/-12)
identifier/identifier.go (+59/-0)
identifier/identifier_test.go (+59/-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)
scripts/software-updates-helper.py (+0/-45)
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:~chipaca/ubuntu-push/merge-automatic
Reviewer Review Type Date Requested Status
Ubuntu Push Hackers Pending
Review via email: mp+230410@code.launchpad.net

Commit message

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

Description of the change

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

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

Subscribers

People subscribed via source and target branches