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