Merge lp:~pedronis/ubuntu-push/automatic-into-krillin-rtm into lp:ubuntu-push/krillin-rtm

Proposed by Samuele Pedroni
Status: Merged
Approved by: Roberto Alsina
Approved revision: 142
Merged at revision: 141
Proposed branch: lp:~pedronis/ubuntu-push/automatic-into-krillin-rtm
Merge into: lp:ubuntu-push/krillin-rtm
Diff against target: 6757 lines (+2552/-853)
72 files modified
.precommit (+21/-19)
PACKAGE_DEPS (+1/-0)
bus/accounts/accounts.go (+310/-0)
bus/accounts/accounts_test.go (+271/-0)
bus/connectivity/connectivity.go (+91/-32)
bus/connectivity/connectivity_test.go (+129/-56)
bus/connectivity/webchecker.go (+12/-3)
bus/connectivity/webchecker_test.go (+9/-3)
bus/endpoint.go (+10/-5)
bus/haptic/haptic.go (+9/-2)
bus/haptic/haptic_test.go (+35/-6)
bus/networkmanager/networkmanager.go (+10/-10)
bus/networkmanager/networkmanager_test.go (+23/-15)
bus/notifications/raw.go (+1/-1)
bus/notifications/raw_test.go (+7/-4)
bus/testing/testing_endpoint.go (+85/-29)
bus/testing/testing_endpoint_test.go (+38/-18)
click/cappinfo/cappinfo.go (+31/-0)
click/cclick/cclick.go (+1/-0)
click/click.go (+4/-0)
click/click_test.go (+14/-0)
client/client.go (+22/-48)
client/client_test.go (+69/-108)
client/service/postal.go (+12/-2)
client/service/postal_test.go (+17/-0)
client/service/service.go (+2/-0)
client/service/service_test.go (+28/-5)
client/session/seenstate/seenstate.go (+6/-1)
client/session/seenstate/sqlseenstate.go (+5/-0)
client/session/seenstate/sqlseenstate_test.go (+9/-0)
client/session/session.go (+208/-82)
client/session/session_test.go (+364/-178)
docs/Makefile (+4/-0)
docs/_common.txt (+58/-44)
docs/example-client/components/ChatClient.qml (+2/-2)
docs/example-client/helloHelper-apparmor.json (+1/-0)
docs/example-client/main.qml (+47/-18)
docs/example-client/manifest.json (+2/-2)
docs/example-server/app.js (+31/-13)
docs/example-server/config/config.js (+1/-1)
docs/example-server/index.html (+2/-0)
docs/example-server/notify-form.html (+61/-6)
docs/example-server/test/app_test.js (+128/-32)
docs/highlevel.txt (+2/-2)
docs/lowlevel.txt (+4/-2)
launch_helper/kindpool_test.go (+1/-1)
logger/logger.go (+12/-7)
logger/logger_test.go (+12/-0)
messaging/messaging_test.go (+14/-7)
poller/poller.go (+8/-2)
server/acceptance/kit/api.go (+14/-3)
server/acceptance/suites/helpers.go (+8/-7)
server/acceptance/suites/suite.go (+2/-1)
server/api/handlers.go (+15/-1)
server/api/handlers_test.go (+22/-6)
server/broker/broker.go (+6/-1)
server/broker/simple/simple.go (+1/-1)
server/broker/simple/suite_test.go (+10/-0)
server/broker/testsuite/suite.go (+31/-29)
server/dev/server.go (+3/-1)
server/listener/listener.go (+12/-2)
server/listener/listener_test.go (+33/-6)
server/runner_devices.go (+2/-2)
server/runner_test.go (+5/-2)
server/session/session.go (+3/-3)
server/session/session_test.go (+7/-6)
server/tlsconfig.go (+12/-0)
sounds/sounds.go (+15/-2)
sounds/sounds_test.go (+44/-3)
testing/helpers.go (+9/-0)
util/redialer.go (+45/-11)
util/redialer_states.gv (+9/-0)
To merge this branch: bzr merge lp:~pedronis/ubuntu-push/automatic-into-krillin-rtm
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Review via email: mp+254285@code.launchpad.net

Commit message

[Bret Barker, Samuele Pedroni]
  * Partial fix of lp:1390663
     - Remove SessionStateSettle sleep on wake, + more debug logging.
     - Don't hold a lock for a long time on handleErrConn, trigger
       autoRedial on Error more actively.

  [John Lenton]
  * Refactor code maintaining session (better fix for lp:1390663)
  * Prune the XDG path from the beginning of accounts-set sound files.
  * Use accounts' settings from sound and haptic.
  * Add an explicit check and log message for nil error on webcheck's
    CopyN.
  * Move logging to info; improve logging of legacy helper errors;
    switch some logs to error from debug.

  [Bret Barker]
  * Add SIGQUIT handler to spit out stack dumps; more logging
    tweaks. [client, server]
  * Log line nums, enabled when logLevel = debug.

  [Samuele Pedroni]
  * Unit test improvements
  * Workaround gc issue with 1.3 and 32 bits.

  [Roberto Ralsina]
  * Example and docs improvements.

  [ Guillermo Gonzalez ]
  * When The server reply 401 on /register, make the DBus call to Register
    return ErrBadAuth instead of ErrBadRequest.
  * Fix click hook for legacy apps
  * Add ClearCookie method to the session and call it from handleAccountsChange.
  * click.AppId.SymbolicIcon() now tries to use X-Ubuntu-SymbolicIcon
    and then fallback to icon+"-symbolic"

To post a comment you must log in.
Revision history for this message
Roberto Alsina (ralsina) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.precommit'
--- .precommit 2014-01-23 10:03:39 +0000
+++ .precommit 2015-03-26 16:42:21 +0000
@@ -5,25 +5,27 @@
5# And put this here-document in ~/.bazaar/plugins/precommit_script.py:5# And put this here-document in ~/.bazaar/plugins/precommit_script.py:
6<<EOF6<<EOF
7import os7import os
8import subprocess8
9from bzrlib.mutabletree import MutableTree9if not os.getenv("SKIP_COMMIT_HOOK"):
10from bzrlib import errors10 import subprocess
1111 from bzrlib.mutabletree import MutableTree
12def start_commit_hook(*_):12 from bzrlib import errors
13 """This hook will execute '.precommit' script from root path of the bazaar13
14 branch. Commit will be canceled if precommit fails."""14 def start_commit_hook(*_):
1515 """This hook will execute '.precommit' script from root path of the bazaar
16 # this hook only makes sense if a precommit file exist.16 branch. Commit will be canceled if precommit fails."""
17 if not os.path.exists(".precommit"):17
18 return18 # this hook only makes sense if a precommit file exist.
19 try:19 if not os.path.exists(".precommit"):
20 subprocess.check_call(os.path.abspath(".precommit"))20 return
21 # if precommit fails (process return not zero) cancel commit.21 try:
22 except subprocess.CalledProcessError:22 subprocess.check_call(os.path.abspath(".precommit"))
23 raise errors.BzrError("pre commit check failed.")23 # if precommit fails (process return not zero) cancel commit.
2424 except subprocess.CalledProcessError:
25MutableTree.hooks.install_named_hook('start_commit', start_commit_hook,25 raise errors.BzrError("pre commit check failed (set SKIP_COMMIT_HOOK to skip).")
26 'Run "precommit" script on start_commit')26
27 MutableTree.hooks.install_named_hook('start_commit', start_commit_hook,
28 'Run "precommit" script on start_commit')
27EOF29EOF
2830
29make check-format # or whatever31make check-format # or whatever
3032
=== modified file 'PACKAGE_DEPS'
--- PACKAGE_DEPS 2014-09-05 10:48:36 +0000
+++ PACKAGE_DEPS 2015-03-26 16:42:21 +0000
@@ -12,3 +12,4 @@
12libclick-0.4-dev12libclick-0.4-dev
13liburl-dispatcher1-dev13liburl-dispatcher1-dev
14libaccounts-glib-dev14libaccounts-glib-dev
15system-image-dbus
1516
=== added directory 'bus/accounts'
=== added file 'bus/accounts/accounts.go'
--- bus/accounts/accounts.go 1970-01-01 00:00:00 +0000
+++ bus/accounts/accounts.go 2015-03-26 16:42:21 +0000
@@ -0,0 +1,310 @@
1/*
2 Copyright 2013-2015 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16// accounts exposes some properties that're stored in org.freedesktop.Accounts
17// (specifically, the ones that we need are all under
18// com.ubuntu.touch.AccountsService.Sound).
19package accounts
20
21import (
22 "fmt"
23 "os/user"
24 "strings"
25 "sync"
26
27 "launchpad.net/go-dbus/v1"
28 "launchpad.net/go-xdg/v0"
29
30 "launchpad.net/ubuntu-push/bus"
31 "launchpad.net/ubuntu-push/logger"
32)
33
34// accounts lives on a well-known bus.Address.
35//
36// Note this one isn't it: the interface is for dbus.properties, and the path
37// is missing the UID.
38var BusAddress bus.Address = bus.Address{
39 Interface: "org.freedesktop.DBus.Properties",
40 Path: "/org/freedesktop/Accounts/User",
41 Name: "org.freedesktop.Accounts",
42}
43
44const accountsSoundIface = "com.ubuntu.touch.AccountsService.Sound"
45
46type Accounts interface {
47 // Start() sets up the asynchronous updating of properties, and does the first update.
48 Start() error
49 // Cancel() stops the asynchronous updating of properties.
50 Cancel() error
51 // SilentMode() tells you whether the device is in silent mode.
52 SilentMode() bool
53 // Vibrate() tells you whether the device is allowed to vibrate.
54 Vibrate() bool
55 // MessageSoundFile() tells you the default sound filename.
56 MessageSoundFile() string
57 String() string
58}
59
60// Accounts tracks the relevant bits of configuration. Nothing directly
61// accessible because it is updated asynchronously, so use the accessors.
62type accounts struct {
63 endp bus.Endpoint
64 log logger.Logger
65 silent bool
66 vibrate bool
67 vibrateSilentMode bool
68 messageSound string
69 cancellable bus.Cancellable
70 lck sync.Mutex
71 updaters map[string]func(dbus.Variant)
72}
73
74// sets up a new Accounts structure, ready to be Start()ed.
75func New(endp bus.Endpoint, log logger.Logger) Accounts {
76 a := &accounts{
77 endp: endp,
78 log: log,
79 }
80
81 a.updaters = map[string]func(dbus.Variant){
82 "SilentMode": a.updateSilentMode,
83 "IncomingMessageVibrate": a.updateVibrate,
84 "IncomingMessageVibrateSilentMode": a.updateVibrateSilentMode,
85 "IncomingMessageSound": a.updateMessageSound,
86 }
87
88 return a
89}
90
91// sets up the asynchronous updating of properties, and does the first update.
92func (a *accounts) Start() error {
93 err := a.startWatch()
94 if err != nil {
95 return err
96 }
97 a.update()
98 return nil
99}
100
101// does sets up the watch on the PropertiesChanged signal. Separate from Start
102// because it holds a lock.
103func (a *accounts) startWatch() error {
104 cancellable, err := a.endp.WatchSignal("PropertiesChanged", a.propsHandler, a.bailoutHandler)
105 if err != nil {
106 a.log.Errorf("unable to watch for property changes: %v", err)
107 return err
108 }
109
110 a.lck.Lock()
111 defer a.lck.Unlock()
112 if a.cancellable != nil {
113 panic("tried to start Accounts twice?")
114 }
115 a.cancellable = cancellable
116
117 return nil
118}
119
120// cancel the asynchronous updating of properties.
121func (a *accounts) Cancel() error {
122 return a.cancellable.Cancel()
123}
124
125// slightly shorter than %#v
126func (a *accounts) String() string {
127 return fmt.Sprintf("&accounts{silent: %t, vibrate: %t, vibratesilent: %t, messageSound: %q}",
128 a.silent, a.vibrate, a.vibrateSilentMode, a.messageSound)
129}
130
131// merely log that the watch loop has bailed; not much we can do.
132func (a *accounts) bailoutHandler() {
133 a.log.Debugf("loop bailed out")
134}
135
136// handle PropertiesChanged, which is described in
137// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
138func (a *accounts) propsHandler(ns ...interface{}) {
139 if len(ns) != 3 {
140 a.log.Errorf("PropertiesChanged delivered %d things instead of 3.", len(ns))
141 return
142 }
143
144 iface, ok := ns[0].(string)
145 if !ok {
146 a.log.Errorf("PropertiesChanged 1st param not a string: %#v.", ns[0])
147 return
148 }
149 if iface != accountsSoundIface {
150 a.log.Debugf("PropertiesChanged for %#v, ignoring.", iface)
151 return
152 }
153 changed, ok := ns[1].(map[interface{}]interface{})
154 if !ok {
155 a.log.Errorf("PropertiesChanged 2nd param not a map: %#v.", ns[1])
156 return
157 }
158 if len(changed) != 0 {
159 // not seen in the wild, but easy to implement properly (ie
160 // using the values we're given) if it starts to
161 // happen. Meanwhile just do a full update.
162 a.log.Infof("PropertiesChanged provided 'changed'; reverting to full update.")
163 a.update()
164 return
165 }
166 invalid, ok := ns[2].([]interface{})
167 if !ok {
168 a.log.Errorf("PropertiesChanged 3rd param not a list of properties: %#v.", ns[2])
169 return
170 }
171 a.log.Debugf("props changed: %#v.", invalid)
172 switch len(invalid) {
173 case 0:
174 // nothing to do?
175 a.log.Debugf("PropertiesChanged 3rd param is empty; doing nothing.")
176 case 1:
177 // the common case right now
178 k, ok := invalid[0].(string)
179 if !ok {
180 a.log.Errorf("PropertiesChanged 3rd param's only entry not a string: %#v.", invalid[0])
181 return
182 }
183 updater, ok := a.updaters[k]
184 if ok {
185 var v dbus.Variant
186 err := a.endp.Call("Get", []interface{}{accountsSoundIface, k}, &v)
187 if err != nil {
188 a.log.Errorf("when calling Get for %s: %v", k, err)
189 return
190 }
191 a.log.Debugf("Get for %s got %#v.", k, v)
192 // updaters must be called with the lock held
193 a.lck.Lock()
194 defer a.lck.Unlock()
195 updater(v)
196 a.log.Debugf("updated %s.", k)
197 }
198 default:
199 // not seen in the wild, but we probably want to drop to a
200 // full update if getting more than one change anyway.
201 a.log.Infof("PropertiesChanged provided more than one 'invalid'; reverting to full update.")
202 a.update()
203 }
204}
205
206func (a *accounts) updateSilentMode(vsilent dbus.Variant) {
207 silent, ok := vsilent.Value.(bool)
208 if !ok {
209 a.log.Errorf("SilentMode needed a bool.")
210 return
211 }
212
213 a.silent = silent
214}
215
216func (a *accounts) updateVibrate(vvibrate dbus.Variant) {
217 vibrate, ok := vvibrate.Value.(bool)
218 if !ok {
219 a.log.Errorf("IncomingMessageVibrate needed a bool.")
220 return
221 }
222
223 a.vibrate = vibrate
224}
225
226func (a *accounts) updateVibrateSilentMode(vvibrateSilentMode dbus.Variant) {
227 vibrateSilentMode, ok := vvibrateSilentMode.Value.(bool)
228 if !ok {
229 a.log.Errorf("IncomingMessageVibrateSilentMode needed a bool.")
230 return
231 }
232
233 a.vibrateSilentMode = vibrateSilentMode
234}
235
236func (a *accounts) updateMessageSound(vsnd dbus.Variant) {
237 snd, ok := vsnd.Value.(string)
238 if !ok {
239 a.log.Errorf("IncomingMessageSound needed a string.")
240 return
241 }
242
243 for _, dir := range xdg.Data.Dirs()[1:] {
244 if dir[len(dir)-1] != '/' {
245 dir += "/"
246 }
247 if strings.HasPrefix(snd, dir) {
248 snd = snd[len(dir):]
249 break
250 }
251 }
252
253 a.messageSound = snd
254}
255
256func (a *accounts) update() {
257 props := make(map[string]dbus.Variant)
258 err := a.endp.Call("GetAll", []interface{}{accountsSoundIface}, &props)
259 if err != nil {
260 a.log.Errorf("when calling GetAll: %v", err)
261 return
262 }
263 a.log.Debugf("GetAll got: %#v", props)
264
265 a.lck.Lock()
266 defer a.lck.Unlock()
267
268 for name, updater := range a.updaters {
269 updater(props[name])
270 }
271}
272
273// is the device in silent mode?
274func (a *accounts) SilentMode() bool {
275 a.lck.Lock()
276 defer a.lck.Unlock()
277
278 return a.silent
279}
280
281// should notifications vibrate?
282func (a *accounts) Vibrate() bool {
283 a.lck.Lock()
284 defer a.lck.Unlock()
285
286 if a.silent {
287 return a.vibrateSilentMode
288 } else {
289 return a.vibrate
290 }
291}
292
293// what is the default sound file?
294func (a *accounts) MessageSoundFile() string {
295 a.lck.Lock()
296 defer a.lck.Unlock()
297
298 return a.messageSound
299}
300
301// the BusAddress should actually end with the UID of the user in question;
302// here we do what's needed to get that.
303func init() {
304 u, err := user.Current()
305 if err != nil {
306 panic(err)
307 }
308
309 BusAddress.Path += u.Uid
310}
0311
=== added file 'bus/accounts/accounts_test.go'
--- bus/accounts/accounts_test.go 1970-01-01 00:00:00 +0000
+++ bus/accounts/accounts_test.go 2015-03-26 16:42:21 +0000
@@ -0,0 +1,271 @@
1/*
2 Copyright 2013-2015 Canonical Ltd.
3
4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published
6 by the Free Software Foundation.
7
8 This program is distributed in the hope that it will be useful, but
9 WITHOUT ANY WARRANTY; without even the implied warranties of
10 MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11 PURPOSE. See the GNU General Public License for more details.
12
13 You should have received a copy of the GNU General Public License along
14 with this program. If not, see <http://www.gnu.org/licenses/>.
15*/
16
17package accounts
18
19import (
20 "errors"
21 "testing"
22
23 "launchpad.net/go-dbus/v1"
24 . "launchpad.net/gocheck"
25
26 testibus "launchpad.net/ubuntu-push/bus/testing"
27 helpers "launchpad.net/ubuntu-push/testing"
28 "launchpad.net/ubuntu-push/testing/condition"
29)
30
31// hook up gocheck
32func TestAcc(t *testing.T) { TestingT(t) }
33
34type AccSuite struct {
35 log *helpers.TestLogger
36}
37
38var _ = Suite(&AccSuite{})
39
40type TestCancellable struct {
41 canceled bool
42 err error
43}
44
45func (t *TestCancellable) Cancel() error {
46 t.canceled = true
47 return t.err
48}
49
50func (s *AccSuite) SetUpTest(c *C) {
51 s.log = helpers.NewTestLogger(c, "debug")
52}
53
54func (s *AccSuite) TestBusAddressPathUidLoaded(c *C) {
55 c.Check(BusAddress.Path, Matches, `.*\d+`)
56}
57
58func (s *AccSuite) TestCancelCancelsCancellable(c *C) {
59 err := errors.New("cancel error")
60 t := &TestCancellable{err: err}
61 a := New(nil, s.log).(*accounts)
62 a.cancellable = t
63
64 c.Check(a.Cancel(), Equals, err)
65 c.Check(t.canceled, Equals, true)
66}
67
68func (s *AccSuite) TestStartReportsWatchError(c *C) {
69 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
70 a := New(endp, s.log).(*accounts)
71 c.Assert(a, NotNil)
72
73 err := a.Start()
74 c.Check(err, NotNil)
75}
76
77func (s *AccSuite) TestStartSetsCancellable(c *C) {
78 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true)
79 a := New(endp, s.log).(*accounts)
80 c.Assert(a, NotNil)
81
82 c.Check(a.cancellable, IsNil)
83 err := a.Start()
84 c.Check(err, IsNil)
85 c.Check(a.cancellable, NotNil)
86 a.Cancel()
87}
88
89func (s *AccSuite) TestStartPanicsIfCalledTwice(c *C) {
90 endp := testibus.NewTestingEndpoint(nil, condition.Work(true), true, true)
91 a := New(endp, s.log).(*accounts)
92 c.Assert(a, NotNil)
93
94 c.Check(a.cancellable, IsNil)
95 err := a.Start()
96 c.Check(err, IsNil)
97 c.Check(func() { a.startWatch() }, PanicMatches, `.* twice\?`)
98 a.Cancel()
99}
100
101func (s *AccSuite) TestUpdateCallsUpdaters(c *C) {
102 endp := testibus.NewTestingEndpoint(nil, condition.Work(true),
103 map[string]dbus.Variant{"x": dbus.Variant{"hello"}})
104 a := New(endp, s.log).(*accounts)
105 c.Assert(a, NotNil)
106 var x dbus.Variant
107 a.updaters = map[string]func(dbus.Variant){
108 "x": func(v dbus.Variant) { x = v },
109 }
110 a.update()
111
112 c.Check(x.Value, Equals, "hello")
113}
114
115func (s *AccSuite) TestUpdateSilentModeBails(c *C) {
116 a := New(nil, s.log).(*accounts)
117 a.updateSilentMode(dbus.Variant{"rubbish"})
118 c.Check(s.log.Captured(), Matches, `(?ms)ERROR SilentMode needed a bool.`)
119}
120
121func (s *AccSuite) TestUpdateSilentModeWorks(c *C) {
122 a := New(nil, s.log).(*accounts)
123 c.Check(a.silent, Equals, false)
124 a.updateSilentMode(dbus.Variant{true})
125 c.Check(a.silent, Equals, true)
126}
127
128func (s *AccSuite) TestUpdateVibrateBails(c *C) {
129 a := New(nil, s.log).(*accounts)
130 a.updateVibrate(dbus.Variant{"rubbish"})
131 c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrate needed a bool.`)
132}
133
134func (s *AccSuite) TestUpdateVibrateWorks(c *C) {
135 a := New(nil, s.log).(*accounts)
136 c.Check(a.vibrate, Equals, false)
137 a.updateVibrate(dbus.Variant{true})
138 c.Check(a.vibrate, Equals, true)
139}
140
141func (s *AccSuite) TestUpdateVibrateSilentModeBails(c *C) {
142 a := New(nil, s.log).(*accounts)
143 a.updateVibrateSilentMode(dbus.Variant{"rubbish"})
144 c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageVibrateSilentMode needed a bool.`)
145}
146
147func (s *AccSuite) TestUpdateVibrateSilentModeWorks(c *C) {
148 a := New(nil, s.log).(*accounts)
149 c.Check(a.vibrateSilentMode, Equals, false)
150 a.updateVibrateSilentMode(dbus.Variant{true})
151 c.Check(a.vibrateSilentMode, Equals, true)
152}
153
154func (s *AccSuite) TestUpdateMessageSoundBails(c *C) {
155 a := New(nil, s.log).(*accounts)
156 a.updateMessageSound(dbus.Variant{42})
157 c.Check(s.log.Captured(), Matches, `(?ms)ERROR IncomingMessageSound needed a string.`)
158}
159
160func (s *AccSuite) TestUpdateMessageSoundWorks(c *C) {
161 a := New(nil, s.log).(*accounts)
162 c.Check(a.messageSound, Equals, "")
163 a.updateMessageSound(dbus.Variant{"xyzzy"})
164 c.Check(a.messageSound, Equals, "xyzzy")
165}
166
167func (s *AccSuite) TestUpdateMessageSoundPrunesXDG(c *C) {
168 a := New(nil, s.log).(*accounts)
169 a.updateMessageSound(dbus.Variant{"/usr/share/xyzzy"})
170 c.Check(a.messageSound, Equals, "xyzzy")
171}
172
173func (s *AccSuite) TestPropsHandler(c *C) {
174 endp := testibus.NewTestingEndpoint(nil, condition.Work(false))
175
176 // testing a series of bad args for propsHandler: none,
177 New(endp, s.log).(*accounts).propsHandler()
178 c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged delivered 0 things.*`)
179 s.log.ResetCapture()
180
181 // bad type for all,
182 New(endp, s.log).(*accounts).propsHandler(nil, nil, nil)
183 c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 1st param not a string.*`)
184 s.log.ResetCapture()
185
186 // wrong interface,
187 New(endp, s.log).(*accounts).propsHandler("xyzzy", nil, nil)
188 c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged for "xyzzy", ignoring\..*`)
189 s.log.ResetCapture()
190
191 // bad type for 2nd and 3rd,
192 New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, nil, nil)
193 c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 2nd param not a map.*`)
194 s.log.ResetCapture()
195
196 // not-seen-in-the-wild 'changed' argument (first non-error outcome),
197 New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{"x": "y"}, nil)
198 // tracking the update() via the GetAll call it generates (which will fail because of the testibus of Work(false) above)
199 c.Check(s.log.Captured(), Matches, `(?ms).*INFO PropertiesChanged provided 'changed'.*ERROR when calling GetAll.*`)
200 s.log.ResetCapture()
201
202 // bad type for 3rd (with empty 2nd),
203 New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, nil)
204 c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param not a list of properties.*`)
205 s.log.ResetCapture()
206
207 // bad type for elements of 3rd,
208 New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{42})
209 c.Check(s.log.Captured(), Matches, `(?ms).*ERROR PropertiesChanged 3rd param's only entry not a string.*`)
210 s.log.ResetCapture()
211
212 // empty 3rd (not an error; hard to test "do ),
213 New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{})
214 c.Check(s.log.Captured(), Matches, `(?ms).*DEBUG PropertiesChanged 3rd param is empty.*`)
215 s.log.ResetCapture()
216
217 // more than one 2rd (also not an error; again looking at the GetAll failure to confirm update() got called),
218 New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"hi", "there"})
219 c.Check(s.log.Captured(), Matches, `(?ms).*INFO.* reverting to full update.*ERROR when calling GetAll.*`)
220 s.log.ResetCapture()
221
222 // bus trouble for a single entry in the 3rd,
223 New(endp, s.log).(*accounts).propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"SilentMode"})
224 c.Check(s.log.Captured(), Matches, `(?ms).*ERROR when calling Get for SilentMode.*`)
225 s.log.ResetCapture()
226
227 // and finally, the common case: a single entry in the 3rd param, that gets updated individually.
228 xOuter := dbus.Variant{"x"}
229 a := New(testibus.NewTestingEndpoint(nil, condition.Work(true), xOuter), s.log).(*accounts)
230 called := false
231 a.updaters = map[string]func(dbus.Variant){"xyzzy": func(x dbus.Variant) {
232 c.Check(x, Equals, xOuter)
233 called = true
234 }}
235 a.propsHandler(accountsSoundIface, map[interface{}]interface{}{}, []interface{}{"xyzzy"})
236 c.Check(called, Equals, true)
237}
238
239func (s *AccSuite) TestSilentMode(c *C) {
240 a := New(nil, s.log).(*accounts)
241 c.Check(a.SilentMode(), Equals, false)
242 a.silent = true
243 c.Check(a.SilentMode(), Equals, true)
244}
245
246func (s *AccSuite) TestVibrate(c *C) {
247 a := New(nil, s.log).(*accounts)
248 c.Check(a.Vibrate(), Equals, false)
249 a.vibrate = true
250 c.Check(a.Vibrate(), Equals, true)
251 a.silent = true
252 c.Check(a.Vibrate(), Equals, false)
253 a.vibrateSilentMode = true
254 c.Check(a.Vibrate(), Equals, true)
255 a.vibrate = false
256 c.Check(a.Vibrate(), Equals, true)
257}
258
259func (s *AccSuite) TestMessageSoundFile(c *C) {
260 a := New(nil, s.log).(*accounts)
261 c.Check(a.MessageSoundFile(), Equals, "")
262 a.messageSound = "xyzzy"
263 c.Check(a.MessageSoundFile(), Equals, "xyzzy")
264}
265
266func (s *AccSuite) TestString(c *C) {
267 a := New(nil, s.log).(*accounts)
268 a.vibrate = true
269 a.messageSound = "x"
270 c.Check(a.String(), Equals, `&accounts{silent: false, vibrate: true, vibratesilent: false, messageSound: "x"}`)
271}
0272
=== modified file 'bus/connectivity/connectivity.go'
--- bus/connectivity/connectivity.go 2015-01-22 17:34:18 +0000
+++ bus/connectivity/connectivity.go 2015-03-26 16:42:21 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 Copyright 2013-2014 Canonical Ltd.2 Copyright 2013-2015 Canonical Ltd.
33
4 This program is free software: you can redistribute it and/or modify it4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published5 under the terms of the GNU General Public License version 3, as published
@@ -24,12 +24,14 @@
2424
25import (25import (
26 "errors"26 "errors"
27 "sync"
28 "time"
29
27 "launchpad.net/ubuntu-push/bus"30 "launchpad.net/ubuntu-push/bus"
28 "launchpad.net/ubuntu-push/bus/networkmanager"31 "launchpad.net/ubuntu-push/bus/networkmanager"
29 "launchpad.net/ubuntu-push/config"32 "launchpad.net/ubuntu-push/config"
30 "launchpad.net/ubuntu-push/logger"33 "launchpad.net/ubuntu-push/logger"
31 "launchpad.net/ubuntu-push/util"34 "launchpad.net/ubuntu-push/util"
32 "time"
33)35)
3436
35// The configuration for ConnectedState, intended to be populated from a config file.37// The configuration for ConnectedState, intended to be populated from a config file.
@@ -45,23 +47,56 @@
45 ConnectivityCheckMD5 string `json:"connectivity_check_md5"`47 ConnectivityCheckMD5 string `json:"connectivity_check_md5"`
46}48}
4749
48type connectedState struct {50// ConnectedState helps tracking connectivity.
51type ConnectedState struct {
49 networkStateCh <-chan networkmanager.State52 networkStateCh <-chan networkmanager.State
50 networkConCh <-chan string53 networkConCh <-chan string
51 config ConnectivityConfig54 config ConnectivityConfig
52 log logger.Logger55 log logger.Logger
53 endp bus.Endpoint56 endp bus.Endpoint
54 connAttempts uint3257 connAttempts uint32
55 webget func(ch chan<- bool)58 webchk Webchecker
56 webgetCh chan bool59 webgetCh chan bool
57 currentState networkmanager.State60 currentState networkmanager.State
58 lastSent bool61 lastSent bool
59 timer *time.Timer62 timer *time.Timer
63 doneLck sync.Mutex
64 done chan struct{}
65 canceled bool
66 stateWatch bus.Cancellable
67 conWatch bus.Cancellable
68}
69
70// New makes a ConnectedState for connectivity tracking.
71//
72// The endpoint need not be dialed; Track() will Dial() and
73// Close() it as it sees fit.
74func New(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger) *ConnectedState {
75 wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log)
76 return &ConnectedState{
77 config: config,
78 log: log,
79 endp: endp,
80 webchk: wg,
81 done: make(chan struct{}),
82 }
83}
84
85// cancel watches if any
86func (cs *ConnectedState) reset() {
87 if cs.stateWatch != nil {
88 cs.stateWatch.Cancel()
89 cs.stateWatch = nil
90 }
91 if cs.conWatch != nil {
92 cs.conWatch.Cancel()
93 cs.conWatch = nil
94 }
60}95}
6196
62// start connects to the bus, gets the initial NetworkManager state, and sets97// start connects to the bus, gets the initial NetworkManager state, and sets
63// up the watch.98// up the watch.
64func (cs *connectedState) start() networkmanager.State {99func (cs *ConnectedState) start() networkmanager.State {
65 var initial networkmanager.State100 var initial networkmanager.State
66 var stateCh <-chan networkmanager.State101 var stateCh <-chan networkmanager.State
67 var primary string102 var primary string
@@ -72,8 +107,9 @@
72 cs.connAttempts += ar.Redial()107 cs.connAttempts += ar.Redial()
73 nm := networkmanager.New(cs.endp, cs.log)108 nm := networkmanager.New(cs.endp, cs.log)
74109
110 cs.reset()
75 // set up the watch111 // set up the watch
76 stateCh, err = nm.WatchState()112 stateCh, cs.stateWatch, err = nm.WatchState()
77 if err != nil {113 if err != nil {
78 cs.log.Debugf("failed to set up the state watch: %s", err)114 cs.log.Debugf("failed to set up the state watch: %s", err)
79 goto Continue115 goto Continue
@@ -87,15 +123,15 @@
87 }123 }
88 cs.log.Debugf("got initial state of %s", initial)124 cs.log.Debugf("got initial state of %s", initial)
89125
126 conCh, cs.conWatch, err = nm.WatchPrimaryConnection()
127 if err != nil {
128 cs.log.Debugf("failed to set up the connection watch: %s", err)
129 goto Continue
130 }
131
90 primary = nm.GetPrimaryConnection()132 primary = nm.GetPrimaryConnection()
91 cs.log.Debugf("primary connection starts as %#v", primary)133 cs.log.Debugf("primary connection starts as %#v", primary)
92134
93 conCh, err = nm.WatchPrimaryConnection()
94 if err != nil {
95 cs.log.Debugf("failed to set up the connection watch: %s", err)
96 goto Continue
97 }
98
99 cs.networkStateCh = stateCh135 cs.networkStateCh = stateCh
100 cs.networkConCh = conCh136 cs.networkConCh = conCh
101137
@@ -107,9 +143,11 @@
107 }143 }
108}144}
109145
110// connectedStateStep takes one step forwards in the “am I connected?”146var errCanceled = errors.New("canceled")
147
148// step takes one step forwards in the “am I connected?”
111// answering state machine.149// answering state machine.
112func (cs *connectedState) connectedStateStep() (bool, error) {150func (cs *ConnectedState) step() (bool, error) {
113 stabilizingTimeout := cs.config.StabilizingTimeout.Duration151 stabilizingTimeout := cs.config.StabilizingTimeout.Duration
114 recheckTimeout := cs.config.RecheckTimeout.Duration152 recheckTimeout := cs.config.RecheckTimeout.Duration
115 log := cs.log153 log := cs.log
@@ -117,6 +155,8 @@
117Loop:155Loop:
118 for {156 for {
119 select {157 select {
158 case <-cs.done:
159 return false, errCanceled
120 case <-cs.networkConCh:160 case <-cs.networkConCh:
121 cs.webgetCh = nil161 cs.webgetCh = nil
122 cs.timer.Reset(stabilizingTimeout)162 cs.timer.Reset(stabilizingTimeout)
@@ -155,8 +195,13 @@
155 case <-cs.timer.C:195 case <-cs.timer.C:
156 if cs.currentState == networkmanager.ConnectedGlobal {196 if cs.currentState == networkmanager.ConnectedGlobal {
157 log.Debugf("connectivity: timer signal, state: ConnectedGlobal, checking...")197 log.Debugf("connectivity: timer signal, state: ConnectedGlobal, checking...")
158 cs.webgetCh = make(chan bool)198 // use a buffered channel, otherwise
159 go cs.webget(cs.webgetCh)199 // we may leak webcheckers that cannot
200 // send their result because we have
201 // cleared webgetCh and wont receive
202 // on it
203 cs.webgetCh = make(chan bool, 1)
204 go cs.webchk.Webcheck(cs.webgetCh)
160 }205 }
161206
162 case connected := <-cs.webgetCh:207 case connected := <-cs.webgetCh:
@@ -173,35 +218,49 @@
173 return cs.lastSent, nil218 return cs.lastSent, nil
174}219}
175220
176// ConnectedState sends the initial NetworkManager state and changes to it221// Track sends the initial NetworkManager state and changes to it
177// over the "out" channel. Sends "false" as soon as it detects trouble, "true"222// over the "out" channel. Sends "false" as soon as it detects trouble, "true"
178// after checking actual connectivity.223// after checking actual connectivity.
179//224//
180// The endpoint need not be dialed; connectivity will Dial() and Close()225func (cs *ConnectedState) Track(out chan<- bool) {
181// it as it sees fit.
182func ConnectedState(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger, out chan<- bool) {
183 wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, 10*time.Second, log)
184 cs := &connectedState{
185 config: config,
186 log: log,
187 endp: endp,
188 webget: wg.Webcheck,
189 }
190226
191Start:227Start:
192 log.Debugf("sending initial 'disconnected'.")228 cs.log.Debugf("sending initial 'disconnected'.")
193 out <- false229 select {
230 case <-cs.done:
231 return
232 case out <- false:
233 }
194 cs.lastSent = false234 cs.lastSent = false
195 cs.currentState = cs.start()235 cs.currentState = cs.start()
236 defer cs.reset()
196 cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration)237 cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration)
197238
198 for {239 for {
199 v, err := cs.connectedStateStep()240 v, err := cs.step()
241 if err == errCanceled {
242 return
243 }
200 if err != nil {244 if err != nil {
201 // tear it all down and start over245 // tear it all down and start over
202 log.Errorf("%s", err)246 cs.log.Errorf("%s", err)
203 goto Start247 goto Start
204 }248 }
205 out <- v249 select {
250 case <-cs.done:
251 return
252 case out <- v:
253 }
254 }
255}
256
257// Cancel stops the ConnectedState machinary.
258func (cs *ConnectedState) Cancel() {
259 cs.doneLck.Lock()
260 defer cs.doneLck.Unlock()
261 if !cs.canceled {
262 cs.canceled = true
263 close(cs.done)
264 cs.webchk.Close()
206 }265 }
207}266}
208267
=== modified file 'bus/connectivity/connectivity_test.go'
--- bus/connectivity/connectivity_test.go 2015-01-22 17:34:18 +0000
+++ bus/connectivity/connectivity_test.go 2015-03-26 16:42:21 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 Copyright 2013-2014 Canonical Ltd.2 Copyright 2013-2015 Canonical Ltd.
33
4 This program is free software: you can redistribute it and/or modify it4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published5 under the terms of the GNU General Public License version 3, as published
@@ -58,22 +58,37 @@
58 s.log = helpers.NewTestLogger(c, "debug")58 s.log = helpers.NewTestLogger(c, "debug")
59}59}
6060
61var (
62 helloCon = dbus.ObjectPath("hello")
63 helloConProps = map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{helloCon}}
64)
65
61/*66/*
62 tests for connectedState's Start() method67 tests for ConnectedState's Start() method
63*/68*/
6469
65// when given a working config and bus, Start() will work70// when given a working config and bus, Start() will work
66func (s *ConnSuite) TestStartWorks(c *C) {71func (s *ConnSuite) TestStartWorks(c *C) {
67 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting))72 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true), uint32(networkmanager.Connecting), helloCon)
68 cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}73 cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
74
75 nopTicker := make(chan []interface{})
76 testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
77 testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
78 defer close(nopTicker)
6979
70 c.Check(cs.start(), Equals, networkmanager.Connecting)80 c.Check(cs.start(), Equals, networkmanager.Connecting)
71}81}
7282
73// if the bus fails a couple of times, we're still OK83// if the bus fails a couple of times, we're still OK
74func (s *ConnSuite) TestStartRetriesConnect(c *C) {84func (s *ConnSuite) TestStartRetriesConnect(c *C) {
75 endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting))85 endp := testingbus.NewTestingEndpoint(condition.Fail2Work(2), condition.Work(true), uint32(networkmanager.Connecting), helloCon)
76 cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}86 cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
87
88 nopTicker := make(chan []interface{})
89 testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
90 testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
91 defer close(nopTicker)
7792
78 c.Check(cs.start(), Equals, networkmanager.Connecting)93 c.Check(cs.start(), Equals, networkmanager.Connecting)
79 c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work94 c.Check(cs.connAttempts, Equals, uint32(3)) // 1 more than the Fail2Work
@@ -81,8 +96,13 @@
8196
82// when the calls to NetworkManager fails for a bit, we're still OK97// when the calls to NetworkManager fails for a bit, we're still OK
83func (s *ConnSuite) TestStartRetriesCall(c *C) {98func (s *ConnSuite) TestStartRetriesCall(c *C) {
84 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting))99 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Fail2Work(5), uint32(networkmanager.Connecting), helloCon)
85 cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}100 cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
101
102 nopTicker := make(chan []interface{})
103 testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
104 testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
105 defer close(nopTicker)
86106
87 c.Check(cs.start(), Equals, networkmanager.Connecting)107 c.Check(cs.start(), Equals, networkmanager.Connecting)
88108
@@ -91,11 +111,19 @@
91111
92// when some of the calls to NetworkManager fails for a bit, we're still OK112// when some of the calls to NetworkManager fails for a bit, we're still OK
93func (s *ConnSuite) TestStartRetriesCall2(c *C) {113func (s *ConnSuite) TestStartRetriesCall2(c *C) {
94 cond := condition.Chain(3, condition.Work(true), 1, condition.Work(false),114 cond := condition.Chain(1, condition.Work(true), 1, condition.Work(false),
95 1, condition.Work(true))115 1, condition.Work(true))
96116
97 endp := testingbus.NewTestingEndpoint(condition.Work(true), cond, uint32(networkmanager.Connecting))117 endp := testingbus.NewTestingEndpoint(condition.Work(true), cond,
98 cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}118 uint32(networkmanager.Connecting), helloCon,
119 uint32(networkmanager.Connecting), helloCon,
120 )
121 cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
122
123 nopTicker := make(chan []interface{})
124 testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
125 testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
126 defer close(nopTicker)
99127
100 c.Check(cs.start(), Equals, networkmanager.Connecting)128 c.Check(cs.start(), Equals, networkmanager.Connecting)
101}129}
@@ -105,17 +133,25 @@
105// watch, we recover and try again.133// watch, we recover and try again.
106func (s *ConnSuite) TestStartRetriesWatch(c *C) {134func (s *ConnSuite) TestStartRetriesWatch(c *C) {
107 nmcond := condition.Chain(135 nmcond := condition.Chain(
108 1, condition.Work(true), // 1 call to nm works136 2, condition.Work(true), // 2 call to nm works
109 1, condition.Work(false), // 1 call to nm fails137 1, condition.Work(false), // 1 call to nm fails
110 0, condition.Work(true)) // and everything works from there on138 0, condition.Work(true)) // and everything works from there on
111 endp := testingbus.NewTestingEndpoint(condition.Work(true), nmcond,139 endp := testingbus.NewTestingEndpoint(condition.Work(true), nmcond,
112 uint32(networkmanager.Connecting),140 uint32(networkmanager.Connecting),
113 uint32(networkmanager.ConnectedGlobal))141 uint32(networkmanager.Connecting),
114 cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}142 helloCon,
143 )
144 cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: endp}
145 watchTicker := make(chan []interface{}, 1)
146 nopTicker := make(chan []interface{})
147 testingbus.SetWatchSource(endp, "StateChanged", watchTicker)
148 testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
149 defer close(nopTicker)
150 defer close(watchTicker)
115151
116 c.Check(cs.start(), Equals, networkmanager.Connecting)152 c.Check(cs.start(), Equals, networkmanager.Connecting)
117 c.Check(cs.connAttempts, Equals, uint32(2))153 c.Check(cs.connAttempts, Equals, uint32(2))
118 c.Check(<-cs.networkStateCh, Equals, networkmanager.Connecting)154 watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
119 c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal)155 c.Check(<-cs.networkStateCh, Equals, networkmanager.ConnectedGlobal)
120}156}
121157
@@ -144,7 +180,7 @@
144 }180 }
145}181}
146182
147func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {183func (rep *racyEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) {
148 if member == "StateChanged" {184 if member == "StateChanged" {
149 // we count never having gotten the state as happening "after" now.185 // we count never having gotten the state as happening "after" now.
150 rep.lock.RLock()186 rep.lock.RLock()
@@ -157,7 +193,7 @@
157 d()193 d()
158 }()194 }()
159 }195 }
160 return nil196 return nil, nil
161}197}
162198
163func (*racyEndpoint) Close() {}199func (*racyEndpoint) Close() {}
@@ -186,7 +222,7 @@
186func (s *ConnSuite) TestStartAvoidsRace(c *C) {222func (s *ConnSuite) TestStartAvoidsRace(c *C) {
187 for delta := time.Second; delta > 1; delta /= 2 {223 for delta := time.Second; delta > 1; delta /= 2 {
188 rep := &racyEndpoint{delta: delta}224 rep := &racyEndpoint{delta: delta}
189 cs := connectedState{config: ConnectivityConfig{}, log: s.log, endp: rep}225 cs := ConnectedState{config: ConnectivityConfig{}, log: s.log, endp: rep}
190 f := Commentf("when delta=%s", delta)226 f := Commentf("when delta=%s", delta)
191 c.Assert(cs.start(), Equals, networkmanager.Connecting, f)227 c.Assert(cs.start(), Equals, networkmanager.Connecting, f)
192 c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f)228 c.Assert(takeNext(cs.networkStateCh), Equals, networkmanager.ConnectedGlobal, f)
@@ -194,9 +230,18 @@
194}230}
195231
196/*232/*
197 tests for connectedStateStep()233 tests for step()
198*/234*/
199235
236type testWebchk func(ch chan<- bool)
237
238func (x testWebchk) Webcheck(ch chan<- bool) {
239 x(ch)
240}
241
242func (x testWebchk) Close() {
243}
244
200func (s *ConnSuite) TestSteps(c *C) {245func (s *ConnSuite) TestSteps(c *C) {
201 var webget_p condition.Interface = condition.Work(true)246 var webget_p condition.Interface = condition.Work(true)
202 recheck_timeout := 50 * time.Millisecond247 recheck_timeout := 50 * time.Millisecond
@@ -205,24 +250,24 @@
205 RecheckTimeout: config.ConfigTimeDuration{recheck_timeout},250 RecheckTimeout: config.ConfigTimeDuration{recheck_timeout},
206 }251 }
207 ch := make(chan networkmanager.State, 10)252 ch := make(chan networkmanager.State, 10)
208 cs := &connectedState{253 cs := &ConnectedState{
209 config: cfg,254 config: cfg,
210 networkStateCh: ch,255 networkStateCh: ch,
211 timer: time.NewTimer(time.Second),256 timer: time.NewTimer(time.Second),
212 log: s.log,257 log: s.log,
213 webget: func(ch chan<- bool) { ch <- webget_p.OK() },258 webchk: testWebchk(func(ch chan<- bool) { ch <- webget_p.OK() }),
214 lastSent: false,259 lastSent: false,
215 }260 }
216 ch <- networkmanager.ConnectedGlobal261 ch <- networkmanager.ConnectedGlobal
217 f, e := cs.connectedStateStep()262 f, e := cs.step()
218 c.Check(e, IsNil)263 c.Check(e, IsNil)
219 c.Check(f, Equals, true)264 c.Check(f, Equals, true)
220 ch <- networkmanager.Disconnected265 ch <- networkmanager.Disconnected
221 ch <- networkmanager.ConnectedGlobal266 ch <- networkmanager.ConnectedGlobal
222 f, e = cs.connectedStateStep()267 f, e = cs.step()
223 c.Check(e, IsNil)268 c.Check(e, IsNil)
224 c.Check(f, Equals, false)269 c.Check(f, Equals, false)
225 f, e = cs.connectedStateStep()270 f, e = cs.step()
226 c.Check(e, IsNil)271 c.Check(e, IsNil)
227 c.Check(f, Equals, true)272 c.Check(f, Equals, true)
228273
@@ -230,7 +275,7 @@
230 webget_p = condition.Fail2Work(1)275 webget_p = condition.Fail2Work(1)
231 ch <- networkmanager.Disconnected276 ch <- networkmanager.Disconnected
232 ch <- networkmanager.ConnectedGlobal277 ch <- networkmanager.ConnectedGlobal
233 f, e = cs.connectedStateStep()278 f, e = cs.step()
234 c.Check(e, IsNil)279 c.Check(e, IsNil)
235 c.Check(f, Equals, false) // first false is from the Disconnected280 c.Check(f, Equals, false) // first false is from the Disconnected
236281
@@ -239,7 +284,7 @@
239 _t := time.NewTimer(recheck_timeout / 2)284 _t := time.NewTimer(recheck_timeout / 2)
240285
241 go func() {286 go func() {
242 f, e := cs.connectedStateStep()287 f, e := cs.step()
243 c.Check(e, IsNil)288 c.Check(e, IsNil)
244 _ch <- f289 _ch <- f
245 }()290 }()
@@ -257,15 +302,15 @@
257 ch <- networkmanager.Disconnected // this should not302 ch <- networkmanager.Disconnected // this should not
258 ch <- networkmanager.ConnectedGlobal // this should trigger a 'true'303 ch <- networkmanager.ConnectedGlobal // this should trigger a 'true'
259304
260 f, e = cs.connectedStateStep()305 f, e = cs.step()
261 c.Check(e, IsNil)306 c.Check(e, IsNil)
262 c.Check(f, Equals, false)307 c.Check(f, Equals, false)
263 f, e = cs.connectedStateStep()308 f, e = cs.step()
264 c.Check(e, IsNil)309 c.Check(e, IsNil)
265 c.Check(f, Equals, true)310 c.Check(f, Equals, true)
266311
267 close(ch) // this should make it error out312 close(ch) // this should make it error out
268 _, e = cs.connectedStateStep()313 _, e = cs.step()
269 c.Check(e, NotNil)314 c.Check(e, NotNil)
270}315}
271316
@@ -285,32 +330,50 @@
285 }330 }
286331
287 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),332 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
288 uint32(networkmanager.ConnectedGlobal),333 uint32(networkmanager.Disconnected),
289 uint32(networkmanager.Disconnected),334 helloCon,
335 uint32(networkmanager.Disconnected),
336 helloCon,
290 )337 )
291338
292 watchTicker := make(chan bool)339 watchTicker := make(chan []interface{})
293 testingbus.SetWatchTicker(endp, watchTicker)340 testingbus.SetWatchSource(endp, "StateChanged", watchTicker)
341 nopTicker := make(chan []interface{})
342 testingbus.SetWatchSource(endp, "PropertiesChanged", nopTicker)
294343
295 out := make(chan bool)344 out := make(chan bool)
296 dt := time.Second / 10345 dt := time.Second / 10
297 timer := time.NewTimer(dt)346 timer := time.NewTimer(dt)
298 go ConnectedState(endp, cfg, s.log, out)347 cs := New(endp, cfg, s.log)
348 defer cs.Cancel()
349 go cs.Track(out)
299 var v bool350 var v bool
300 expecteds := []struct {351 expecteds := []struct {
301 p bool352 p bool
302 s string353 s string
303 n int354 todo string
304 }{355 }{
305 {false, "first state is always false", 0},356 {false, "first state is always false", ""},
306 {true, "then it should be true as per ConnectedGlobal above", 0},357 {true, "then it should be true as per ConnectedGlobal above", "ConnectedGlobal"},
307 {false, "then it should be false (Disconnected)", 2},358 {false, "then it should be false (Disconnected)", "Disconnected"},
308 {false, "then it should be false again because it's restarted", 2},359 {false, "then it should be false again because it's restarted", "close"},
309 }360 }
310361
362 defer func() {
363 if watchTicker != nil {
364 close(watchTicker)
365 }
366 }()
367 defer close(nopTicker)
311 for i, expected := range expecteds {368 for i, expected := range expecteds {
312 for j := 0; j < expected.n; j++ {369 switch expected.todo {
313 watchTicker <- true370 case "ConnectedGlobal":
371 watchTicker <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
372 case "Disconnected":
373 watchTicker <- []interface{}{uint32(networkmanager.Disconnected)}
374 case "close":
375 close(watchTicker)
376 watchTicker = nil
314 }377 }
315 timer.Reset(dt)378 timer.Reset(dt)
316 select {379 select {
@@ -335,31 +398,41 @@
335398
336 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),399 endp := testingbus.NewTestingEndpoint(condition.Work(true), condition.Work(true),
337 uint32(networkmanager.ConnectedGlobal),400 uint32(networkmanager.ConnectedGlobal),
338 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("hello")}},401 helloCon,
339 )402 )
340403
341 watchTicker := make(chan bool)404 watchTicker := make(chan []interface{})
342 testingbus.SetWatchTicker(endp, watchTicker)405 testingbus.SetWatchSource(endp, "PropertiesChanged", watchTicker)
406 nopTicker := make(chan []interface{})
407 testingbus.SetWatchSource(endp, "StateChanged", nopTicker)
343408
344 out := make(chan bool)409 out := make(chan bool)
345 dt := time.Second / 10410 dt := time.Second / 10
346 timer := time.NewTimer(dt)411 timer := time.NewTimer(dt)
347 go ConnectedState(endp, cfg, s.log, out)412 cs := New(endp, cfg, s.log)
413 defer cs.Cancel()
414 go cs.Track(out)
348 var v bool415 var v bool
349 expecteds := []struct {416 expecteds := []struct {
350 p bool417 p bool
351 s string418 s string
352 n int419 changedConn bool
353 }{420 }{
354 {false, "first state is always false", 0},421 {false, "first state is always false", false},
355 {true, "then it should be true as per ConnectedGlobal above", 0},422 {true, "then it should be true as per ConnectedGlobal above", false},
356 {false, "then, false (PrimaryConnection changed)", 2},423 {false, "then, false (PrimaryConnection changed)", true},
357 {true, "then it should be true (webcheck passed)", 0},424 {true, "then it should be true (webcheck passed)", false},
358 }425 }
359426
427 defer func() {
428 if watchTicker != nil {
429 close(watchTicker)
430 }
431 }()
432 defer close(nopTicker)
360 for i, expected := range expecteds {433 for i, expected := range expecteds {
361 for j := 0; j < expected.n; j++ {434 if expected.changedConn {
362 watchTicker <- true435 watchTicker <- []interface{}{helloConProps}
363 }436 }
364 timer.Reset(dt)437 timer.Reset(dt)
365 select {438 select {
366439
=== modified file 'bus/connectivity/webchecker.go'
--- bus/connectivity/webchecker.go 2015-01-22 17:34:18 +0000
+++ bus/connectivity/webchecker.go 2015-03-26 16:42:21 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 Copyright 2013-2014 Canonical Ltd.2 Copyright 2013-2015 Canonical Ltd.
33
4 This program is free software: you can redistribute it and/or modify it4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published5 under the terms of the GNU General Public License version 3, as published
@@ -40,6 +40,8 @@
40 // contents match the target. If so, then it sends true; if anything40 // contents match the target. If so, then it sends true; if anything
41 // fails, it sends false.41 // fails, it sends false.
42 Webcheck(chan<- bool)42 Webcheck(chan<- bool)
43 // Close idle connections.
44 Close()
43}45}
4446
45type webchecker struct {47type webchecker struct {
@@ -72,8 +74,11 @@
72 hash := md5.New()74 hash := md5.New()
73 _, err = io.CopyN(hash, response.Body, 1024)75 _, err = io.CopyN(hash, response.Body, 1024)
74 if err != io.EOF {76 if err != io.EOF {
75 wb.log.Errorf("reading %s, expecting EOF, got: %v",77 if err == nil {
76 wb.url, err)78 wb.log.Errorf("reading %s, but response body is larger than 1k.", wb.url)
79 } else {
80 wb.log.Errorf("reading %s, expecting EOF, got: %v", wb.url, err)
81 }
77 ch <- false82 ch <- false
78 return83 return
79 }84 }
@@ -86,3 +91,7 @@
86 ch <- false91 ch <- false
87 }92 }
88}93}
94
95func (wb *webchecker) Close() {
96 wb.cli.Transport.(*http13.Transport).CloseIdleConnections()
97}
8998
=== modified file 'bus/connectivity/webchecker_test.go'
--- bus/connectivity/webchecker_test.go 2014-03-20 12:24:33 +0000
+++ bus/connectivity/webchecker_test.go 2015-03-26 16:42:21 +0000
@@ -1,5 +1,5 @@
1/*1/*
2 Copyright 2013-2014 Canonical Ltd.2 Copyright 2013-2015 Canonical Ltd.
33
4 This program is free software: you can redistribute it and/or modify it4 This program is free software: you can redistribute it and/or modify it
5 under the terms of the GNU General Public License version 3, as published5 under the terms of the GNU General Public License version 3, as published
@@ -18,7 +18,6 @@
1818
19import (19import (
20 . "launchpad.net/gocheck"20 . "launchpad.net/gocheck"
21 "launchpad.net/ubuntu-push/logger"
22 helpers "launchpad.net/ubuntu-push/testing"21 helpers "launchpad.net/ubuntu-push/testing"
23 "launchpad.net/ubuntu-push/util"22 "launchpad.net/ubuntu-push/util"
24 "net/http"23 "net/http"
@@ -28,7 +27,7 @@
2827
29type WebcheckerSuite struct {28type WebcheckerSuite struct {
30 timeouts []time.Duration29 timeouts []time.Duration
31 log logger.Logger30 log *helpers.TestLogger
32}31}
3332
34var _ = Suite(&WebcheckerSuite{})33var _ = Suite(&WebcheckerSuite{})
@@ -82,6 +81,7 @@
82 defer ts.Close()81 defer ts.Close()
8382
84 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)83 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)
84 defer ck.Close()
85 ch := make(chan bool, 1)85 ch := make(chan bool, 1)
86 ck.Webcheck(ch)86 ck.Webcheck(ch)
87 c.Check(<-ch, Equals, true)87 c.Check(<-ch, Equals, true)
@@ -90,6 +90,7 @@
90// Webchecker sends false if the download fails.90// Webchecker sends false if the download fails.
91func (s *WebcheckerSuite) TestActualFails(c *C) {91func (s *WebcheckerSuite) TestActualFails(c *C) {
92 ck := NewWebchecker("garbage://", "", 5*time.Second, s.log)92 ck := NewWebchecker("garbage://", "", 5*time.Second, s.log)
93 defer ck.Close()
93 ch := make(chan bool, 1)94 ch := make(chan bool, 1)
94 ck.Webcheck(ch)95 ck.Webcheck(ch)
95 c.Check(<-ch, Equals, false)96 c.Check(<-ch, Equals, false)
@@ -101,9 +102,11 @@
101 defer ts.Close()102 defer ts.Close()
102103
103 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)104 ck := NewWebchecker(ts.URL, staticHash, 5*time.Second, s.log)
105 defer ck.Close()
104 ch := make(chan bool, 1)106 ch := make(chan bool, 1)
105 ck.Webcheck(ch)107 ck.Webcheck(ch)
106 c.Check(<-ch, Equals, false)108 c.Check(<-ch, Equals, false)
109 c.Check(s.log.Captured(), Matches, "(?ism).*content mismatch.*")
107}110}
108111
109// Webchecker sends false if the download is too big112// Webchecker sends false if the download is too big
@@ -112,9 +115,11 @@
112 defer ts.Close()115 defer ts.Close()
113116
114 ck := NewWebchecker(ts.URL, bigHash, 5*time.Second, s.log)117 ck := NewWebchecker(ts.URL, bigHash, 5*time.Second, s.log)
118 defer ck.Close()
115 ch := make(chan bool, 1)119 ch := make(chan bool, 1)
116 ck.Webcheck(ch)120 ck.Webcheck(ch)
117 c.Check(<-ch, Equals, false)121 c.Check(<-ch, Equals, false)
122 c.Check(s.log.Captured(), Matches, "(?ism).*larger than 1k.*")
118}123}
119124
120// Webchecker sends false if the request timeouts125// Webchecker sends false if the request timeouts
@@ -130,6 +135,7 @@
130 }()135 }()
131136
132 ck := NewWebchecker(ts.URL, bigHash, time.Second, s.log)137 ck := NewWebchecker(ts.URL, bigHash, time.Second, s.log)
138 defer ck.Close()
133 ch := make(chan bool, 1)139 ch := make(chan bool, 1)
134 ck.Webcheck(ch)140 ck.Webcheck(ch)
135 c.Check(<-ch, Equals, false)141 c.Check(<-ch, Equals, false)
136142
=== modified file 'bus/endpoint.go'
--- bus/endpoint.go 2015-01-22 17:34:18 +0000
+++ bus/endpoint.go 2015-03-26 16:42:21 +0000
@@ -35,10 +35,15 @@
35type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error)35type BusMethod func(string, []interface{}, []interface{}) ([]interface{}, error)
36type DispatchMap map[string]BusMethod36type DispatchMap map[string]BusMethod
3737
38// Cancellable can be canceled.
39type Cancellable interface {
40 Cancel() error
41}
42
38// bus.Endpoint represents the DBus connection itself.43// bus.Endpoint represents the DBus connection itself.
39type Endpoint interface {44type Endpoint interface {
40 GrabName(allowReplacement bool) <-chan error45 GrabName(allowReplacement bool) <-chan error
41 WatchSignal(member string, f func(...interface{}), d func()) error46 WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error)
42 WatchMethod(DispatchMap, string, ...interface{})47 WatchMethod(DispatchMap, string, ...interface{})
43 Signal(string, string, []interface{}) error48 Signal(string, string, []interface{}) error
44 Call(member string, args []interface{}, rvs ...interface{}) error49 Call(member string, args []interface{}, rvs ...interface{}) error
@@ -123,16 +128,16 @@
123// sends the values over a channel, and d() would close the channel.128// sends the values over a channel, and d() would close the channel.
124//129//
125// XXX: untested130// XXX: untested
126func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) error {131func (endp *endpoint) WatchSignal(member string, f func(...interface{}), d func()) (Cancellable, error) {
127 watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member)132 watch, err := endp.proxy.WatchSignal(endp.addr.Interface, member)
128 if err != nil {133 if err != nil {
129 endp.log.Debugf("failed to set up the watch: %s", err)134 endp.log.Debugf("failed to set up the watch: %s", err)
130 return err135 return nil, err
131 }136 }
132137
133 go endp.unpackMessages(watch, f, d, member)138 go endp.unpackMessages(watch, f, d, member)
134139
135 return nil140 return watch, nil
136}141}
137142
138// Call() invokes the provided member method (on the name, path and143// Call() invokes the provided member method (on the name, path and
@@ -324,6 +329,6 @@
324 }329 }
325 f(endp.unpackOneMsg(msg, member)...)330 f(endp.unpackOneMsg(msg, member)...)
326 }331 }
327 endp.log.Errorf("got not-OK from %s watch", member)332 endp.log.Debugf("got not-OK from %s watch", member)
328 d()333 d()
329}334}
330335
=== modified file 'bus/haptic/haptic.go'
--- bus/haptic/haptic.go 2014-08-08 01:07:38 +0000
+++ bus/haptic/haptic.go 2015-03-26 16:42:21 +0000
@@ -20,6 +20,7 @@
2020
21import (21import (
22 "launchpad.net/ubuntu-push/bus"22 "launchpad.net/ubuntu-push/bus"
23 "launchpad.net/ubuntu-push/bus/accounts"
23 "launchpad.net/ubuntu-push/click"24 "launchpad.net/ubuntu-push/click"
24 "launchpad.net/ubuntu-push/launch_helper"25 "launchpad.net/ubuntu-push/launch_helper"
25 "launchpad.net/ubuntu-push/logger"26 "launchpad.net/ubuntu-push/logger"
@@ -36,12 +37,13 @@
36type Haptic struct {37type Haptic struct {
37 bus bus.Endpoint38 bus bus.Endpoint
38 log logger.Logger39 log logger.Logger
40 acc accounts.Accounts
39 fallback *launch_helper.Vibration41 fallback *launch_helper.Vibration
40}42}
4143
42// New returns a new Haptic that'll use the provided bus.Endpoint44// New returns a new Haptic that'll use the provided bus.Endpoint
43func New(endp bus.Endpoint, log logger.Logger, fallback *launch_helper.Vibration) *Haptic {45func New(endp bus.Endpoint, log logger.Logger, acc accounts.Accounts, fallback *launch_helper.Vibration) *Haptic {
44 return &Haptic{endp, log, fallback}46 return &Haptic{endp, log, acc, fallback}
45}47}
4648
47// Present presents the notification via a vibrate pattern49// Present presents the notification via a vibrate pattern
@@ -50,6 +52,11 @@
50 panic("please check notification is not nil before calling present")52 panic("please check notification is not nil before calling present")
51 }53 }
5254
55 if !haptic.acc.Vibrate() {
56 haptic.log.Debugf("[%s] vibrate disabled by user.", nid)
57 return false
58 }
59
53 vib := notification.Vibration(haptic.fallback)60 vib := notification.Vibration(haptic.fallback)
54 if vib == nil {61 if vib == nil {
55 haptic.log.Debugf("[%s] notification has no Vibrate.", nid)62 haptic.log.Debugf("[%s] notification has no Vibrate.", nid)
5663
=== modified file 'bus/haptic/haptic_test.go'
--- bus/haptic/haptic_test.go 2014-08-08 01:07:38 +0000
+++ bus/haptic/haptic_test.go 2015-03-26 16:42:21 +0000
@@ -35,20 +35,36 @@
35type hapticSuite struct {35type hapticSuite struct {
36 log *helpers.TestLogger36 log *helpers.TestLogger
37 app *click.AppId37 app *click.AppId
38}38 acc *mockAccounts
39}
40
41type mockAccounts struct {
42 vib bool
43 sil bool
44 snd string
45 err error
46}
47
48func (m *mockAccounts) Start() error { return m.err }
49func (m *mockAccounts) Cancel() error { return m.err }
50func (m *mockAccounts) SilentMode() bool { return m.sil }
51func (m *mockAccounts) Vibrate() bool { return m.vib }
52func (m *mockAccounts) MessageSoundFile() string { return m.snd }
53func (m *mockAccounts) String() string { return "<mockAccounts>" }
3954
40var _ = Suite(&hapticSuite{})55var _ = Suite(&hapticSuite{})
4156
42func (hs *hapticSuite) SetUpTest(c *C) {57func (hs *hapticSuite) SetUpTest(c *C) {
43 hs.log = helpers.NewTestLogger(c, "debug")58 hs.log = helpers.NewTestLogger(c, "debug")
44 hs.app = clickhelp.MustParseAppId("com.example.test_test-app_0")59 hs.app = clickhelp.MustParseAppId("com.example.test_test-app_0")
60 hs.acc = &mockAccounts{true, false, "xyzzy", nil}
45}61}
4662
47// checks that Present() actually calls VibratePattern63// checks that Present() actually calls VibratePattern
48func (hs *hapticSuite) TestPresentPresents(c *C) {64func (hs *hapticSuite) TestPresentPresents(c *C) {
49 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))65 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
5066
51 ec := New(endp, hs.log, nil)67 ec := New(endp, hs.log, hs.acc, nil)
52 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100], "repeat": 2}`)}68 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100], "repeat": 2}`)}
53 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)69 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
54 callArgs := testibus.GetCallArgs(endp)70 callArgs := testibus.GetCallArgs(endp)
@@ -61,7 +77,7 @@
61func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) {77func (hs *hapticSuite) TestPresentDefaultsRepeatTo1(c *C) {
62 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))78 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
6379
64 ec := New(endp, hs.log, nil)80 ec := New(endp, hs.log, hs.acc, nil)
65 // note: no Repeat:81 // note: no Repeat:
66 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100]}`)}82 notif := launch_helper.Notification{RawVibration: json.RawMessage(`{"pattern": [200, 100]}`)}
67 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)83 c.Check(ec.Present(hs.app, "nid", &notif), Equals, true)
@@ -76,7 +92,7 @@
76func (hs *hapticSuite) TestSkipIfMissing(c *C) {92func (hs *hapticSuite) TestSkipIfMissing(c *C) {
77 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))93 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
7894
79 ec := New(endp, hs.log, nil)95 ec := New(endp, hs.log, hs.acc, nil)
80 // no Vibration in the notificaton96 // no Vibration in the notificaton
81 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false)97 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{}), Equals, false)
82 // empty Vibration98 // empty Vibration
@@ -85,11 +101,24 @@
85 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: json.RawMessage(`{}`)}), Equals, false)101 c.Check(ec.Present(hs.app, "", &launch_helper.Notification{RawVibration: json.RawMessage(`{}`)}), Equals, false)
86}102}
87103
104// check that Present() does not present if the accounts' Vibrate() returns false
105func (hs *hapticSuite) TestPresentSkipsIfVibrateDisabled(c *C) {
106 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
107 fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}
108
109 ec := New(endp, hs.log, hs.acc, fallback)
110 notif := launch_helper.Notification{RawVibration: json.RawMessage(`true`)}
111 c.Assert(ec.Present(hs.app, "nid", &notif), Equals, true)
112 // ok!
113 hs.acc.vib = false
114 c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)
115}
116
88// check that Present() panics if the notification is nil117// check that Present() panics if the notification is nil
89func (hs *hapticSuite) TestPanicsIfNil(c *C) {118func (hs *hapticSuite) TestPanicsIfNil(c *C) {
90 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))119 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
91120
92 ec := New(endp, hs.log, nil)121 ec := New(endp, hs.log, hs.acc, nil)
93 // no notification at all122 // no notification at all
94 c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`)123 c.Check(func() { ec.Present(hs.app, "", nil) }, Panics, `please check notification is not nil before calling present`)
95}124}
@@ -99,7 +128,7 @@
99 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))128 endp := testibus.NewTestingEndpoint(nil, condition.Work(true))
100 fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}129 fallback := &launch_helper.Vibration{Pattern: []uint32{200, 100}, Repeat: 2}
101130
102 ec := New(endp, hs.log, fallback)131 ec := New(endp, hs.log, hs.acc, fallback)
103 notif := launch_helper.Notification{RawVibration: json.RawMessage(`false`)}132 notif := launch_helper.Notification{RawVibration: json.RawMessage(`false`)}
104 c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)133 c.Check(ec.Present(hs.app, "nid", &notif), Equals, false)
105 notif = launch_helper.Notification{RawVibration: json.RawMessage(`true`)}134 notif = launch_helper.Notification{RawVibration: json.RawMessage(`true`)}
106135
=== modified file 'bus/networkmanager/networkmanager.go'
--- bus/networkmanager/networkmanager.go 2015-01-22 17:34:18 +0000
+++ bus/networkmanager/networkmanager.go 2015-03-26 16:42:21 +0000
@@ -42,13 +42,13 @@
42 GetState() State42 GetState() State
43 // WatchState listens for changes to NetworkManager's state, and sends43 // WatchState listens for changes to NetworkManager's state, and sends
44 // them out over the channel returned.44 // them out over the channel returned.
45 WatchState() (<-chan State, error)45 WatchState() (<-chan State, bus.Cancellable, error)
46 // GetPrimaryConnection fetches and returns NetworkManager's current46 // GetPrimaryConnection fetches and returns NetworkManager's current
47 // primary connection.47 // primary connection.
48 GetPrimaryConnection() string48 GetPrimaryConnection() string
49 // WatchPrimaryConnection listens for changes of NetworkManager's49 // WatchPrimaryConnection listens for changes of NetworkManager's
50 // Primary Connection, and sends it out over the channel returned.50 // Primary Connection, and sends it out over the channel returned.
51 WatchPrimaryConnection() (<-chan string, error)51 WatchPrimaryConnection() (<-chan string, bus.Cancellable, error)
52}52}
5353
54type networkManager struct {54type networkManager struct {
@@ -85,9 +85,9 @@
85 return State(v)85 return State(v)
86}86}
8787
88func (nm *networkManager) WatchState() (<-chan State, error) {88func (nm *networkManager) WatchState() (<-chan State, bus.Cancellable, error) {
89 ch := make(chan State)89 ch := make(chan State)
90 err := nm.bus.WatchSignal("StateChanged",90 w, err := nm.bus.WatchSignal("StateChanged",
91 func(ns ...interface{}) {91 func(ns ...interface{}) {
92 stint, ok := ns[0].(uint32)92 stint, ok := ns[0].(uint32)
93 if !ok {93 if !ok {
@@ -101,10 +101,10 @@
101 func() { close(ch) })101 func() { close(ch) })
102 if err != nil {102 if err != nil {
103 nm.log.Debugf("Failed to set up the watch: %s", err)103 nm.log.Debugf("Failed to set up the watch: %s", err)
104 return nil, err104 return nil, nil, err
105 }105 }
106106
107 return ch, nil107 return ch, w, nil
108}108}
109109
110func (nm *networkManager) GetPrimaryConnection() string {110func (nm *networkManager) GetPrimaryConnection() string {
@@ -124,9 +124,9 @@
124 return string(v)124 return string(v)
125}125}
126126
127func (nm *networkManager) WatchPrimaryConnection() (<-chan string, error) {127func (nm *networkManager) WatchPrimaryConnection() (<-chan string, bus.Cancellable, error) {
128 ch := make(chan string)128 ch := make(chan string)
129 err := nm.bus.WatchSignal("PropertiesChanged",129 w, err := nm.bus.WatchSignal("PropertiesChanged",
130 func(ppsi ...interface{}) {130 func(ppsi ...interface{}) {
131 pps, ok := ppsi[0].(map[string]dbus.Variant)131 pps, ok := ppsi[0].(map[string]dbus.Variant)
132 if !ok {132 if !ok {
@@ -147,8 +147,8 @@
147 }, func() { close(ch) })147 }, func() { close(ch) })
148 if err != nil {148 if err != nil {
149 nm.log.Debugf("failed to set up the watch: %s", err)149 nm.log.Debugf("failed to set up the watch: %s", err)
150 return nil, err150 return nil, nil, err
151 }151 }
152152
153 return ch, nil153 return ch, w, nil
154}154}
155155
=== modified file 'bus/networkmanager/networkmanager_test.go'
--- bus/networkmanager/networkmanager_test.go 2014-04-04 12:01:42 +0000
+++ bus/networkmanager/networkmanager_test.go 2015-03-26 16:42:21 +0000
@@ -90,8 +90,9 @@
90func (s *NMSuite) TestWatchState(c *C) {90func (s *NMSuite) TestWatchState(c *C) {
91 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal))91 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), uint32(Unknown), uint32(Asleep), uint32(ConnectedGlobal))
92 nm := New(tc, s.log)92 nm := New(tc, s.log)
93 ch, err := nm.WatchState()93 ch, w, err := nm.WatchState()
94 c.Check(err, IsNil)94 c.Assert(err, IsNil)
95 defer w.Cancel()
95 l := []State{<-ch, <-ch, <-ch}96 l := []State{<-ch, <-ch, <-ch}
96 c.Check(l, DeepEquals, []State{Unknown, Asleep, ConnectedGlobal})97 c.Check(l, DeepEquals, []State{Unknown, Asleep, ConnectedGlobal})
97}98}
@@ -99,7 +100,7 @@
99// WatchState returns on error if the dbus call fails100// WatchState returns on error if the dbus call fails
100func (s *NMSuite) TestWatchStateFails(c *C) {101func (s *NMSuite) TestWatchStateFails(c *C) {
101 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)102 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
102 _, err := nm.WatchState()103 _, _, err := nm.WatchState()
103 c.Check(err, NotNil)104 c.Check(err, NotNil)
104}105}
105106
@@ -107,8 +108,9 @@
107func (s *NMSuite) TestWatchStateClosesOnWatchBail(c *C) {108func (s *NMSuite) TestWatchStateClosesOnWatchBail(c *C) {
108 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))109 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
109 nm := New(tc, s.log)110 nm := New(tc, s.log)
110 ch, err := nm.WatchState()111 ch, w, err := nm.WatchState()
111 c.Check(err, IsNil)112 c.Assert(err, IsNil)
113 defer w.Cancel()
112 _, ok := <-ch114 _, ok := <-ch
113 c.Check(ok, Equals, false)115 c.Check(ok, Equals, false)
114}116}
@@ -117,8 +119,9 @@
117func (s *NMSuite) TestWatchStateSurvivesRubbishValues(c *C) {119func (s *NMSuite) TestWatchStateSurvivesRubbishValues(c *C) {
118 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")120 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")
119 nm := New(tc, s.log)121 nm := New(tc, s.log)
120 ch, err := nm.WatchState()122 ch, w, err := nm.WatchState()
121 c.Check(err, IsNil)123 c.Assert(err, IsNil)
124 defer w.Cancel()
122 _, ok := <-ch125 _, ok := <-ch
123 c.Check(ok, Equals, false)126 c.Check(ok, Equals, false)
124}127}
@@ -164,8 +167,9 @@
164 mkPriConMap("/b/2"),167 mkPriConMap("/b/2"),
165 mkPriConMap("/c/3"))168 mkPriConMap("/c/3"))
166 nm := New(tc, s.log)169 nm := New(tc, s.log)
167 ch, err := nm.WatchPrimaryConnection()170 ch, w, err := nm.WatchPrimaryConnection()
168 c.Check(err, IsNil)171 c.Assert(err, IsNil)
172 defer w.Cancel()
169 l := []string{<-ch, <-ch, <-ch}173 l := []string{<-ch, <-ch, <-ch}
170 c.Check(l, DeepEquals, []string{"/a/1", "/b/2", "/c/3"})174 c.Check(l, DeepEquals, []string{"/a/1", "/b/2", "/c/3"})
171}175}
@@ -173,7 +177,7 @@
173// WatchPrimaryConnection returns on error if the dbus call fails177// WatchPrimaryConnection returns on error if the dbus call fails
174func (s *NMSuite) TestWatchPrimaryConnectionFails(c *C) {178func (s *NMSuite) TestWatchPrimaryConnectionFails(c *C) {
175 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)179 nm := New(testingbus.NewTestingEndpoint(nil, condition.Work(false)), s.log)
176 _, err := nm.WatchPrimaryConnection()180 _, _, err := nm.WatchPrimaryConnection()
177 c.Check(err, NotNil)181 c.Check(err, NotNil)
178}182}
179183
@@ -181,8 +185,9 @@
181func (s *NMSuite) TestWatchPrimaryConnectionClosesOnWatchBail(c *C) {185func (s *NMSuite) TestWatchPrimaryConnectionClosesOnWatchBail(c *C) {
182 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))186 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true))
183 nm := New(tc, s.log)187 nm := New(tc, s.log)
184 ch, err := nm.WatchPrimaryConnection()188 ch, w, err := nm.WatchPrimaryConnection()
185 c.Check(err, IsNil)189 c.Assert(err, IsNil)
190 defer w.Cancel()
186 _, ok := <-ch191 _, ok := <-ch
187 c.Check(ok, Equals, false)192 c.Check(ok, Equals, false)
188}193}
@@ -191,8 +196,9 @@
191func (s *NMSuite) TestWatchPrimaryConnectionSurvivesRubbishValues(c *C) {196func (s *NMSuite) TestWatchPrimaryConnectionSurvivesRubbishValues(c *C) {
192 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")197 tc := testingbus.NewTestingEndpoint(nil, condition.Work(true), "a")
193 nm := New(tc, s.log)198 nm := New(tc, s.log)
194 ch, err := nm.WatchPrimaryConnection()199 ch, w, err := nm.WatchPrimaryConnection()
195 c.Assert(err, IsNil)200 c.Assert(err, IsNil)
201 defer w.Cancel()
196 _, ok := <-ch202 _, ok := <-ch
197 c.Check(ok, Equals, false)203 c.Check(ok, Equals, false)
198}204}
@@ -204,8 +210,9 @@
204 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},210 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},
205 )211 )
206 nm := New(tc, s.log)212 nm := New(tc, s.log)
207 ch, err := nm.WatchPrimaryConnection()213 ch, w, err := nm.WatchPrimaryConnection()
208 c.Assert(err, IsNil)214 c.Assert(err, IsNil)
215 defer w.Cancel()
209 v, ok := <-ch216 v, ok := <-ch
210 c.Check(ok, Equals, true)217 c.Check(ok, Equals, true)
211 c.Check(v, Equals, "42")218 c.Check(v, Equals, "42")
@@ -218,8 +225,9 @@
218 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},225 map[string]dbus.Variant{"PrimaryConnection": dbus.Variant{dbus.ObjectPath("42")}},
219 )226 )
220 nm := New(tc, s.log)227 nm := New(tc, s.log)
221 ch, err := nm.WatchPrimaryConnection()228 ch, w, err := nm.WatchPrimaryConnection()
222 c.Assert(err, IsNil)229 c.Assert(err, IsNil)
230 defer w.Cancel()
223 v, ok := <-ch231 v, ok := <-ch
224 c.Check(ok, Equals, true)232 c.Check(ok, Equals, true)
225 c.Check(v, Equals, "42")233 c.Check(v, Equals, "42")
226234
=== modified file 'bus/notifications/raw.go'
--- bus/notifications/raw.go 2015-01-22 17:34:18 +0000
+++ bus/notifications/raw.go 2015-03-26 16:42:21 +0000
@@ -93,7 +93,7 @@
93// and sends them over the channel provided93// and sends them over the channel provided
94func (raw *RawNotifications) WatchActions() (<-chan *RawAction, error) {94func (raw *RawNotifications) WatchActions() (<-chan *RawAction, error) {
95 ch := make(chan *RawAction)95 ch := make(chan *RawAction)
96 err := raw.bus.WatchSignal("ActionInvoked",96 _, err := raw.bus.WatchSignal("ActionInvoked",
97 func(ns ...interface{}) {97 func(ns ...interface{}) {
98 if len(ns) != 2 {98 if len(ns) != 2 {
99 raw.log.Debugf("ActionInvoked delivered %d things instead of 2", len(ns))99 raw.log.Debugf("ActionInvoked delivered %d things instead of 2", len(ns))
100100
=== modified file 'bus/notifications/raw_test.go'
--- bus/notifications/raw_test.go 2014-08-15 10:33:04 +0000
+++ bus/notifications/raw_test.go 2015-03-26 16:42:21 +0000
@@ -111,14 +111,16 @@
111 errstr string111 errstr string
112 endp bus.Endpoint112 endp bus.Endpoint
113 works bool113 works bool
114 src chan []interface{}
114}115}
115116
116func (s *RawSuite) TestWatchActionsToleratesDBusWeirdness(c *C) {117func (s *RawSuite) TestWatchActionsToleratesDBusWeirdness(c *C) {
117 X := func(errstr string, args ...interface{}) tst {118 X := func(errstr string, args ...interface{}) tst {
118 endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true), args)119 endp := testibus.NewMultiValuedTestingEndpoint(nil, condition.Work(true))
119 // stop the endpoint from closing the channel:120 src := make(chan []interface{}, 1)
120 testibus.SetWatchTicker(endp, make(chan bool))121 testibus.SetWatchSource(endp, "ActionInvoked", src)
121 return tst{errstr, endp, errstr == ""}122 src <- args
123 return tst{errstr, endp, errstr == "", src}
122 }124 }
123125
124 ts := []tst{126 ts := []tst{
@@ -146,6 +148,7 @@
146 }148 }
147 c.Check(s.log.Captured(), Matches, `(?ms).*`+t.errstr+`.*`)149 c.Check(s.log.Captured(), Matches, `(?ms).*`+t.errstr+`.*`)
148 s.log.ResetCapture()150 s.log.ResetCapture()
151 close(t.src)
149 }152 }
150153
151}154}
152155
=== modified file 'bus/testing/testing_endpoint.go'
--- bus/testing/testing_endpoint.go 2014-07-04 23:00:42 +0000
+++ bus/testing/testing_endpoint.go 2015-03-26 16:42:21 +0000
@@ -36,13 +36,15 @@
36}36}
3737
38type testingEndpoint struct {38type testingEndpoint struct {
39 dialCond condition.Interface39 dialCond condition.Interface
40 callCond condition.Interface40 callCond condition.Interface
41 retvals [][]interface{}41 usedLck sync.Mutex
42 watchTicker chan bool42 used int
43 watchLck sync.RWMutex43 retvals [][]interface{}
44 callArgs []callArgs44 watchSources map[string]chan []interface{}
45 callArgsLck sync.RWMutex45 watchLck sync.RWMutex
46 callArgs []callArgs
47 callArgsLck sync.RWMutex
46}48}
4749
48// Build a bus.Endpoint that calls OK() on its condition before returning50// Build a bus.Endpoint that calls OK() on its condition before returning
@@ -51,7 +53,7 @@
51// NOTE: Call() always returns the first return value; Watch() will provide53// NOTE: Call() always returns the first return value; Watch() will provide
52// each of them in turn, irrespective of whether Call has been called.54// each of them in turn, irrespective of whether Call has been called.
53func NewMultiValuedTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvalses ...[]interface{}) bus.Endpoint {55func NewMultiValuedTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvalses ...[]interface{}) bus.Endpoint {
54 return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses}56 return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})}
55}57}
5658
57func NewTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvals ...interface{}) bus.Endpoint {59func NewTestingEndpoint(dialCond condition.Interface, callCond condition.Interface, retvals ...interface{}) bus.Endpoint {
@@ -59,15 +61,15 @@
59 for i, x := range retvals {61 for i, x := range retvals {
60 retvalses[i] = []interface{}{x}62 retvalses[i] = []interface{}{x}
61 }63 }
62 return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses}64 return &testingEndpoint{dialCond: dialCond, callCond: callCond, retvals: retvalses, watchSources: make(map[string]chan []interface{})}
63}65}
6466
65// If SetWatchTicker is called with a non-nil watchTicker, it is used67// If SetWatchSource is called with a non-nil watchSource, it is used
66// instead of the default timeout to wait while sending values over68// instead of the default timeout and retvals to get values to send
67// WatchSignal. Set it to nil again to restore default behaviour.69// over WatchSignal. Set it to nil again to restore default behaviour.
68func SetWatchTicker(tc bus.Endpoint, watchTicker chan bool) {70func SetWatchSource(tc bus.Endpoint, member string, watchSource chan []interface{}) {
69 tc.(*testingEndpoint).watchLck.Lock()71 tc.(*testingEndpoint).watchLck.Lock()
70 tc.(*testingEndpoint).watchTicker = watchTicker72 tc.(*testingEndpoint).watchSources[member] = watchSource
71 tc.(*testingEndpoint).watchLck.Unlock()73 tc.(*testingEndpoint).watchLck.Unlock()
72}74}
7375
@@ -78,27 +80,77 @@
78 return tc.(*testingEndpoint).callArgs80 return tc.(*testingEndpoint).callArgs
79}81}
8082
83type watchCancel struct {
84 done chan struct{}
85 cancelled chan struct{}
86 lck sync.Mutex
87 member string
88}
89
90// this waits for actual cancelllation for test convenience
91func (wc *watchCancel) Cancel() error {
92 wc.lck.Lock()
93 defer wc.lck.Unlock()
94 if wc.cancelled != nil {
95 close(wc.cancelled)
96 wc.cancelled = nil
97 <-wc.done
98 }
99 return nil
100}
101
81// See Endpoint's WatchSignal. This WatchSignal will check its condition to102// See Endpoint's WatchSignal. This WatchSignal will check its condition to
82// decide whether to return an error, or provide each of its return values103// decide whether to return an error, or provide each of its return values
83func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) error {104// or values from the previously set watchSource for member.
105func (tc *testingEndpoint) WatchSignal(member string, f func(...interface{}), d func()) (bus.Cancellable, error) {
84 if tc.callCond.OK() {106 if tc.callCond.OK() {
107 cancelled := make(chan struct{})
108 done := make(chan struct{})
85 go func() {109 go func() {
86 for _, v := range tc.retvals {110 tc.watchLck.RLock()
87 f(v...)111 source := tc.watchSources[member]
88 tc.watchLck.RLock()112 tc.watchLck.RUnlock()
89 ticker := tc.watchTicker113 if source == nil {
90 tc.watchLck.RUnlock()114 tc.usedLck.Lock()
91 if ticker != nil {115 idx := tc.used
92 <-ticker116 tc.used++
93 } else {117 tc.usedLck.Unlock()
94 time.Sleep(10 * time.Millisecond)118 source = make(chan []interface{})
119 go func() {
120 Feed:
121 for _, v := range tc.retvals[idx:] {
122 select {
123 case source <- v:
124 case <-cancelled:
125 break Feed
126 }
127 select {
128 case <-time.After(10 * time.Millisecond):
129 case <-cancelled:
130 break Feed
131 }
132 }
133 close(source)
134 }()
135 }
136 Receive:
137 for {
138 select {
139 case v, ok := <-source:
140 if !ok {
141 break Receive
142 }
143 f(v...)
144 case <-cancelled:
145 break Receive
95 }146 }
96 }147 }
97 d()148 d()
149 close(done)
98 }()150 }()
99 return nil151 return &watchCancel{cancelled: cancelled, done: done, member: member}, nil
100 } else {152 } else {
101 return errors.New("no way")153 return nil, errors.New("no way")
102 }154 }
103}155}
104156
@@ -112,20 +164,24 @@
112 if tc.callCond.OK() {164 if tc.callCond.OK() {
113 expected := len(rvs)165 expected := len(rvs)
114 var provided int166 var provided int
115 if len(tc.retvals) == 0 {167 tc.usedLck.Lock()
168 idx := tc.used
169 tc.used++
170 tc.usedLck.Unlock()
171 if len(tc.retvals) <= idx {
116 if expected != 0 {172 if expected != 0 {
117 panic("No return values provided!")173 panic("No return values provided!")
118 }174 }
119 provided = 0175 provided = 0
120 } else {176 } else {
121 provided = len(tc.retvals[0])177 provided = len(tc.retvals[idx])
122 }178 }
123 if provided != expected {179 if provided != expected {
124 return errors.New("provided/expected return vals mismatch")180 return errors.New("provided/expected return vals mismatch")
125 }181 }
126 if provided != 0 {182 if provided != 0 {
127 x := dbus.NewMethodCallMessage("", "", "", "")183 x := dbus.NewMethodCallMessage("", "", "", "")
128 err := x.AppendArgs(tc.retvals[0]...)184 err := x.AppendArgs(tc.retvals[idx]...)
129 if err != nil {185 if err != nil {
130 return err186 return err
131 }187 }
132188
=== modified file 'bus/testing/testing_endpoint_test.go'
--- bus/testing/testing_endpoint_test.go 2014-07-04 23:00:42 +0000
+++ bus/testing/testing_endpoint_test.go 2015-03-26 16:42:21 +0000
@@ -17,11 +17,13 @@
17package testing17package testing
1818
19import (19import (
20 "testing"
21 "time"
22
20 . "launchpad.net/gocheck"23 . "launchpad.net/gocheck"
24
21 "launchpad.net/ubuntu-push/bus"25 "launchpad.net/ubuntu-push/bus"
22 "launchpad.net/ubuntu-push/testing/condition"26 "launchpad.net/ubuntu-push/testing/condition"
23 "testing"
24 "time"
25)27)
2628
27// hook up gocheck29// hook up gocheck
@@ -100,8 +102,9 @@
100 var m, n uint32 = 42, 17102 var m, n uint32 = 42, 17
101 endp := NewTestingEndpoint(nil, condition.Work(true), m, n)103 endp := NewTestingEndpoint(nil, condition.Work(true), m, n)
102 ch := make(chan uint32)104 ch := make(chan uint32)
103 e := endp.WatchSignal("what", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) })105 w, e := endp.WatchSignal("which", func(us ...interface{}) { ch <- us[0].(uint32) }, func() { close(ch) })
104 c.Check(e, IsNil)106 c.Assert(e, IsNil)
107 defer w.Cancel()
105 c.Check(<-ch, Equals, m)108 c.Check(<-ch, Equals, m)
106 c.Check(<-ch, Equals, n)109 c.Check(<-ch, Equals, n)
107}110}
@@ -110,8 +113,9 @@
110func (s *TestingEndpointSuite) TestWatchDestructor(c *C) {113func (s *TestingEndpointSuite) TestWatchDestructor(c *C) {
111 endp := NewTestingEndpoint(nil, condition.Work(true))114 endp := NewTestingEndpoint(nil, condition.Work(true))
112 ch := make(chan uint32)115 ch := make(chan uint32)
113 e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })116 w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
114 c.Check(e, IsNil)117 c.Assert(e, IsNil)
118 defer w.Cancel()
115 _, ok := <-ch119 _, ok := <-ch
116 c.Check(ok, Equals, false)120 c.Check(ok, Equals, false)
117}121}
@@ -130,25 +134,28 @@
130// Test that WatchSignal() with a negative condition returns an error.134// Test that WatchSignal() with a negative condition returns an error.
131func (s *TestingEndpointSuite) TestWatchFails(c *C) {135func (s *TestingEndpointSuite) TestWatchFails(c *C) {
132 endp := NewTestingEndpoint(nil, condition.Work(false))136 endp := NewTestingEndpoint(nil, condition.Work(false))
133 e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {})137 w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() {})
134 c.Check(e, NotNil)138 c.Check(e, NotNil)
139 c.Check(w, IsNil)
135}140}
136141
137// Test WatchSignal can use the WatchTicker instead of a timeout (if142// Test WatchSignal can use a watchSource instead of a timeout and retvals (if
138// the former is not nil)143// the former is not nil)
139func (s *TestingEndpointSuite) TestWatchTicker(c *C) {144func (s *TestingEndpointSuite) TestWatchSources(c *C) {
140 watchTicker := make(chan bool, 3)145 watchTicker := make(chan []interface{}, 3)
141 watchTicker <- true146 watchTicker <- []interface{}{true}
142 watchTicker <- true147 watchTicker <- []interface{}{true}
143 watchTicker <- true148 watchTicker <- []interface{}{true}
144 c.Assert(len(watchTicker), Equals, 3)149 c.Assert(len(watchTicker), Equals, 3)
145150
146 endp := NewTestingEndpoint(nil, condition.Work(true), 0, 0)151 endp := NewTestingEndpoint(nil, condition.Work(true), 0, 0)
147 SetWatchTicker(endp, watchTicker)152 SetWatchSource(endp, "what", watchTicker)
148 ch := make(chan int)153 ch := make(chan int)
149 e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })154 w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
150 c.Check(e, IsNil)155 c.Assert(e, IsNil)
156 defer w.Cancel()
151157
158 close(watchTicker)
152 // wait for the destructor to be called159 // wait for the destructor to be called
153 select {160 select {
154 case <-time.Tick(10 * time.Millisecond):161 case <-time.Tick(10 * time.Millisecond):
@@ -156,8 +163,21 @@
156 case <-ch:163 case <-ch:
157 }164 }
158165
159 // now if all went well, the ticker will have been tuck twice.166 // now if all went well, the ticker will have been exhausted.
160 c.Assert(len(watchTicker), Equals, 1)167 c.Assert(len(watchTicker), Equals, 0)
168}
169
170// Test that WatchSignal() calls the destructor callback when canceled.
171func (s *TestingEndpointSuite) TestWatchCancel(c *C) {
172 endp := NewTestingEndpoint(nil, condition.Work(true))
173 ch := make(chan uint32)
174 w, e := endp.WatchSignal("what", func(us ...interface{}) {}, func() { close(ch) })
175 c.Assert(e, IsNil)
176 defer w.Cancel()
177 SetWatchSource(endp, "what", make(chan []interface{}))
178 w.Cancel()
179 _, ok := <-ch
180 c.Check(ok, Equals, false)
161}181}
162182
163// Tests that GetProperty() works183// Tests that GetProperty() works
164184
=== modified file 'click/cappinfo/cappinfo.go'
--- click/cappinfo/cappinfo.go 2014-07-08 23:23:13 +0000
+++ click/cappinfo/cappinfo.go 2015-03-26 16:42:21 +0000
@@ -37,6 +37,26 @@
37 g_free (desktop_id);37 g_free (desktop_id);
38 return filename;38 return filename;
39}39}
40
41gchar* app_symbolic_icon_from_desktop_id (gchar* desktop_id) {
42 gchar* x_symbolic_icon;
43 GIcon* symbolic_icon;
44 GDesktopAppInfo* app_info = g_desktop_app_info_new (desktop_id);
45 if (app_info != NULL) {
46 if((x_symbolic_icon = g_desktop_app_info_get_string(app_info, "X-Ubuntu-SymbolicIcon"))) {
47 GFile *file;
48 file = g_file_new_for_path(x_symbolic_icon);
49 symbolic_icon = g_file_icon_new (file);
50 g_object_unref (file);
51 g_free(x_symbolic_icon);
52 g_object_unref (app_info);
53 return g_icon_to_string(symbolic_icon);
54 }
55 g_object_unref (app_info);
56 }
57 g_free (desktop_id);
58 return NULL;
59}
40*/60*/
41import "C"61import "C"
4262
@@ -45,3 +65,14 @@
45 defer C.g_free((C.gpointer)(name))65 defer C.g_free((C.gpointer)(name))
46 return C.GoString((*C.char)(name))66 return C.GoString((*C.char)(name))
47}67}
68
69func appSymbolicIconFromDesktopId(desktopId string) string {
70 name := C.app_symbolic_icon_from_desktop_id((*C.gchar)(C.CString(desktopId)))
71 if name == nil {
72 return ""
73 }
74 defer C.g_free((C.gpointer)(name))
75 return C.GoString((*C.char)(name))
76}
77
78var AppSymbolicIconFromDesktopId = appSymbolicIconFromDesktopId
4879
=== modified file 'click/cclick/cclick.go'
--- click/cclick/cclick.go 2014-07-07 22:04:30 +0000
+++ click/cclick/cclick.go 2015-03-26 16:42:21 +0000
@@ -51,6 +51,7 @@
51 }51 }
52 ccu.cref = cref52 ccu.cref = cref
53 runtime.SetFinalizer(holder, func(interface{}) {53 runtime.SetFinalizer(holder, func(interface{}) {
54 ccu.cref = nil // 1.3 gc gets confused otherwise
54 C.g_object_unref((C.gpointer)(cref))55 C.g_object_unref((C.gpointer)(cref))
55 })56 })
56 return nil57 return nil
5758
=== modified file 'click/click.go'
--- click/click.go 2014-08-15 10:32:51 +0000
+++ click/click.go 2015-03-26 16:42:21 +0000
@@ -146,6 +146,10 @@
146var symbolic = _symbolic146var symbolic = _symbolic
147147
148func (app *AppId) SymbolicIcon() string {148func (app *AppId) SymbolicIcon() string {
149 symbolicIcon := cappinfo.AppSymbolicIconFromDesktopId(app.DesktopId())
150 if symbolicIcon != "" {
151 return symbolicIcon
152 }
149 return symbolic(app.Icon())153 return symbolic(app.Icon())
150}154}
151155
152156
=== modified file 'click/click_test.go'
--- click/click_test.go 2014-08-15 10:32:51 +0000
+++ click/click_test.go 2015-03-26 16:42:21 +0000
@@ -22,6 +22,8 @@
22 "testing"22 "testing"
2323
24 . "launchpad.net/gocheck"24 . "launchpad.net/gocheck"
25
26 "launchpad.net/ubuntu-push/click/cappinfo"
25)27)
2628
27func TestClick(t *testing.T) { TestingT(t) }29func TestClick(t *testing.T) { TestingT(t) }
@@ -200,3 +202,15 @@
200 c.Assert(err, IsNil)202 c.Assert(err, IsNil)
201 c.Check(app.SymbolicIcon(), Equals, "xyzzy")203 c.Check(app.SymbolicIcon(), Equals, "xyzzy")
202}204}
205
206func (s *clickSuite) TestSymbolicFromDesktopFile(c *C) {
207 orig := cappinfo.AppSymbolicIconFromDesktopId
208 cappinfo.AppSymbolicIconFromDesktopId = func(desktopId string) string {
209 return "/foo/symbolic"
210 }
211 defer func() {
212 cappinfo.AppSymbolicIconFromDesktopId = orig
213 }()
214 app, _ := ParseAppId("com.ubuntu.clock_clock_1.2")
215 c.Check(app.SymbolicIcon(), Equals, "/foo/symbolic")
216}
203217
=== modified file 'client/client.go'
--- client/client.go 2015-01-22 17:34:18 +0000
+++ client/client.go 2015-03-26 16:42:21 +0000
@@ -115,8 +115,7 @@
115 systemImageEndp bus.Endpoint115 systemImageEndp bus.Endpoint
116 systemImageInfo *systemimage.InfoResult116 systemImageInfo *systemimage.InfoResult
117 connCh chan bool117 connCh chan bool
118 hasConnectivity bool118 session session.ClientSession
119 session *session.ClientSession
120 sessionConnectedCh chan uint32119 sessionConnectedCh chan uint32
121 pushService PushService120 pushService PushService
122 postalService PostalService121 postalService PostalService
@@ -125,16 +124,20 @@
125 installedChecker click.InstalledChecker124 installedChecker click.InstalledChecker
126 poller poller.Poller125 poller poller.Poller
127 accountsCh <-chan accounts.Changed126 accountsCh <-chan accounts.Changed
127 // session-side channels
128 broadcastCh chan *session.BroadcastNotification
129 notificationsCh chan session.AddressedNotification
128}130}
129131
130// Creates a new Ubuntu Push Notifications client-side daemon that will use132// Creates a new Ubuntu Push Notifications client-side daemon that will use
131// the given configuration file.133// the given configuration file.
132func NewPushClient(configPath string, leveldbPath string) *PushClient {134func NewPushClient(configPath string, leveldbPath string) *PushClient {
133 client := new(PushClient)135 return &PushClient{
134 client.configPath = configPath136 configPath: configPath,
135 client.leveldbPath = leveldbPath137 leveldbPath: leveldbPath,
136138 broadcastCh: make(chan *session.BroadcastNotification),
137 return client139 notificationsCh: make(chan session.AddressedNotification),
140 }
138}141}
139142
140var newIdentifier = identifier.New143var newIdentifier = identifier.New
@@ -206,6 +209,8 @@
206 AuthGetter: client.getAuthorization,209 AuthGetter: client.getAuthorization,
207 AuthURL: client.config.SessionURL,210 AuthURL: client.config.SessionURL,
208 AddresseeChecker: client,211 AddresseeChecker: client,
212 BroadcastCh: client.broadcastCh,
213 NotificationsCh: client.notificationsCh,
209 }214 }
210}215}
211216
@@ -280,8 +285,10 @@
280285
281// takeTheBus starts the connection(s) to D-Bus and sets up associated event channels286// takeTheBus starts the connection(s) to D-Bus and sets up associated event channels
282func (client *PushClient) takeTheBus() error {287func (client *PushClient) takeTheBus() error {
283 go connectivity.ConnectedState(client.connectivityEndp,288 fmt.Println("FOO")
284 client.config.ConnectivityConfig, client.log, client.connCh)289 cs := connectivity.New(client.connectivityEndp,
290 client.config.ConnectivityConfig, client.log)
291 go cs.Track(client.connCh)
285 util.NewAutoRedialer(client.systemImageEndp).Redial()292 util.NewAutoRedialer(client.systemImageEndp).Redial()
286 sysimg := systemimage.New(client.systemImageEndp, client.log)293 sysimg := systemimage.New(client.systemImageEndp, client.log)
287 info, err := sysimg.Info()294 info, err := sysimg.Info()
@@ -306,6 +313,7 @@
306 return err313 return err
307 }314 }
308 client.session = sess315 client.session = sess
316 sess.KeepConnection()
309 client.poller = poller.New(client.derivePollerSetup())317 client.poller = poller.New(client.derivePollerSetup())
310 return nil318 return nil
311}319}
@@ -374,29 +382,6 @@
374 }382 }
375}383}
376384
377// handleConnState deals with connectivity events
378func (client *PushClient) handleConnState(hasConnectivity bool) {
379 client.log.Debugf("handleConnState: %v", hasConnectivity)
380 if client.hasConnectivity == hasConnectivity {
381 // nothing to do!
382 return
383 }
384 client.hasConnectivity = hasConnectivity
385 client.session.Close()
386 if hasConnectivity {
387 client.session.AutoRedial(client.sessionConnectedCh)
388 }
389}
390
391// handleErr deals with the session erroring out of its loop
392func (client *PushClient) handleErr(err error) {
393 // if we're not connected, we don't really care
394 client.log.Errorf("session exited: %s", err)
395 if client.hasConnectivity {
396 client.session.AutoRedial(client.sessionConnectedCh)
397 }
398}
399
400// filterBroadcastNotification finds out if the notification is about an actual385// filterBroadcastNotification finds out if the notification is about an actual
401// upgrade for the device. It expects msg.Decoded entries to look386// upgrade for the device. It expects msg.Decoded entries to look
402// like:387// like:
@@ -467,28 +452,18 @@
467 return nil452 return nil
468}453}
469454
470// handleAccountsChange deals with the user adding or removing (or
471// changing) the u1 account used to auth
472func (client *PushClient) handleAccountsChange() {
473 client.log.Infof("U1 account changed; restarting session")
474 client.session.ClearCookie()
475 client.session.Close()
476}
477
478// doLoop connects events with their handlers455// doLoop connects events with their handlers
479func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, errhandler func(error), unregisterhandler func(*click.AppId), accountshandler func()) {456func (client *PushClient) doLoop(connhandler func(bool), bcasthandler func(*session.BroadcastNotification) error, ucasthandler func(session.AddressedNotification) error, unregisterhandler func(*click.AppId), accountshandler func()) {
480 for {457 for {
481 select {458 select {
482 case <-client.accountsCh:459 case <-client.accountsCh:
483 accountshandler()460 accountshandler()
484 case state := <-client.connCh:461 case state := <-client.connCh:
485 connhandler(state)462 connhandler(state)
486 case bcast := <-client.session.BroadcastCh:463 case bcast := <-client.broadcastCh:
487 bcasthandler(bcast)464 bcasthandler(bcast)
488 case aucast := <-client.session.NotificationsCh:465 case aucast := <-client.notificationsCh:
489 ucasthandler(aucast)466 ucasthandler(aucast)
490 case err := <-client.session.ErrCh:
491 errhandler(err)
492 case count := <-client.sessionConnectedCh:467 case count := <-client.sessionConnectedCh:
493 client.log.Debugf("session connected after %d attempts", count)468 client.log.Debugf("session connected after %d attempts", count)
494 case app := <-client.unregisterCh:469 case app := <-client.unregisterCh:
@@ -510,12 +485,11 @@
510485
511// Loop calls doLoop with the "real" handlers486// Loop calls doLoop with the "real" handlers
512func (client *PushClient) Loop() {487func (client *PushClient) Loop() {
513 client.doLoop(client.handleConnState,488 client.doLoop(client.session.HasConnectivity,
514 client.handleBroadcastNotification,489 client.handleBroadcastNotification,
515 client.handleUnicastNotification,490 client.handleUnicastNotification,
516 client.handleErr,
517 client.handleUnregister,491 client.handleUnregister,
518 client.handleAccountsChange,492 client.session.ResetCookie,
519 )493 )
520}494}
521495
522496
=== modified file 'client/client_test.go'
--- client/client_test.go 2015-01-22 17:34:18 +0000
+++ client/client_test.go 2015-03-26 16:42:21 +0000
@@ -27,9 +27,11 @@
27 "os"27 "os"
28 "path/filepath"28 "path/filepath"
29 "reflect"29 "reflect"
30 //"runtime"
30 "testing"31 "testing"
31 "time"32 "time"
3233
34 "launchpad.net/go-dbus/v1"
33 . "launchpad.net/gocheck"35 . "launchpad.net/gocheck"
3436
35 "launchpad.net/ubuntu-push/accounts"37 "launchpad.net/ubuntu-push/accounts"
@@ -41,7 +43,6 @@
41 clickhelp "launchpad.net/ubuntu-push/click/testing"43 clickhelp "launchpad.net/ubuntu-push/click/testing"
42 "launchpad.net/ubuntu-push/client/service"44 "launchpad.net/ubuntu-push/client/service"
43 "launchpad.net/ubuntu-push/client/session"45 "launchpad.net/ubuntu-push/client/session"
44 "launchpad.net/ubuntu-push/client/session/seenstate"
45 "launchpad.net/ubuntu-push/config"46 "launchpad.net/ubuntu-push/config"
46 "launchpad.net/ubuntu-push/identifier"47 "launchpad.net/ubuntu-push/identifier"
47 idtesting "launchpad.net/ubuntu-push/identifier/testing"48 idtesting "launchpad.net/ubuntu-push/identifier/testing"
@@ -203,6 +204,10 @@
203 cs.writeTestConfig(nil)204 cs.writeTestConfig(nil)
204}205}
205206
207func (cs *clientSuite) TearDownTest(c *C) {
208 //helpers.DumpGoroutines()
209}
210
206type sqlientSuite struct{ clientSuite }211type sqlientSuite struct{ clientSuite }
207212
208func (s *sqlientSuite) SetUpSuite(c *C) {213func (s *sqlientSuite) SetUpSuite(c *C) {
@@ -421,6 +426,8 @@
421 AuthGetter: func(string) string { return "" },426 AuthGetter: func(string) string { return "" },
422 AuthURL: "xyzzy://",427 AuthURL: "xyzzy://",
423 AddresseeChecker: cli,428 AddresseeChecker: cli,
429 BroadcastCh: make(chan *session.BroadcastNotification),
430 NotificationsCh: make(chan session.AddressedNotification),
424 }431 }
425 // sanity check that we are looking at all fields432 // sanity check that we are looking at all fields
426 vExpected := reflect.ValueOf(expected)433 vExpected := reflect.ValueOf(expected)
@@ -434,6 +441,11 @@
434 conf := cli.deriveSessionConfig(info)441 conf := cli.deriveSessionConfig(info)
435 // compare authGetter by string442 // compare authGetter by string
436 c.Check(fmt.Sprintf("%#v", conf.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization))443 c.Check(fmt.Sprintf("%#v", conf.AuthGetter), Equals, fmt.Sprintf("%#v", cli.getAuthorization))
444 // channels are ok as long as non-nil
445 conf.BroadcastCh = nil
446 conf.NotificationsCh = nil
447 expected.BroadcastCh = nil
448 expected.NotificationsCh = nil
437 // and set it to nil449 // and set it to nil
438 conf.AuthGetter = nil450 conf.AuthGetter = nil
439 expected.AuthGetter = nil451 expected.AuthGetter = nil
@@ -515,10 +527,18 @@
515/*****************************************************************527/*****************************************************************
516 derivePollerSetup tests528 derivePollerSetup tests
517******************************************************************/529******************************************************************/
530type derivePollerSession struct{}
531
532func (s *derivePollerSession) ResetCookie() {}
533func (s *derivePollerSession) State() session.ClientSessionState { return session.Unknown }
534func (s *derivePollerSession) HasConnectivity(bool) {}
535func (s *derivePollerSession) KeepConnection() error { return nil }
536func (s *derivePollerSession) StopKeepConnection() {}
537
518func (cs *clientSuite) TestDerivePollerSetup(c *C) {538func (cs *clientSuite) TestDerivePollerSetup(c *C) {
519 cs.writeTestConfig(map[string]interface{}{})539 cs.writeTestConfig(map[string]interface{}{})
520 cli := NewPushClient(cs.configPath, cs.leveldbPath)540 cli := NewPushClient(cs.configPath, cs.leveldbPath)
521 cli.session = new(session.ClientSession)541 cli.session = new(derivePollerSession)
522 err := cli.configure()542 err := cli.configure()
523 c.Assert(err, IsNil)543 c.Assert(err, IsNil)
524 expected := &poller.PollerSetup{544 expected := &poller.PollerSetup{
@@ -647,11 +667,19 @@
647 // testing endpoints667 // testing endpoints
648 cCond := condition.Fail2Work(7)668 cCond := condition.Fail2Work(7)
649 cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true),669 cEndp := testibus.NewTestingEndpoint(cCond, condition.Work(true),
650 uint32(networkmanager.ConnectedGlobal),670 uint32(networkmanager.Connecting),
671 dbus.ObjectPath("hello"),
672 uint32(networkmanager.Connecting),
673 dbus.ObjectPath("hello"),
651 )674 )
652 siCond := condition.Fail2Work(2)675 siCond := condition.Fail2Work(2)
653 siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}})676 siEndp := testibus.NewMultiValuedTestingEndpoint(siCond, condition.Work(true), []interface{}{int32(101), "mako", "daily", "Unknown", map[string]string{}})
654 testibus.SetWatchTicker(cEndp, make(chan bool))677 tickerCh := make(chan []interface{})
678 nopTickerCh := make(chan []interface{})
679 testibus.SetWatchSource(cEndp, "StateChanged", tickerCh)
680 testibus.SetWatchSource(cEndp, "PropertiesChanged", nopTickerCh)
681 defer close(tickerCh)
682 defer close(nopTickerCh)
655 // ok, create the thing683 // ok, create the thing
656 cli := NewPushClient(cs.configPath, cs.leveldbPath)684 cli := NewPushClient(cs.configPath, cs.leveldbPath)
657 cli.log = cs.log685 cli.log = cs.log
@@ -667,6 +695,7 @@
667 c.Assert(cli.takeTheBus(), IsNil)695 c.Assert(cli.takeTheBus(), IsNil)
668696
669 c.Check(takeNextBool(cli.connCh), Equals, false)697 c.Check(takeNextBool(cli.connCh), Equals, false)
698 tickerCh <- []interface{}{uint32(networkmanager.ConnectedGlobal)}
670 c.Check(takeNextBool(cli.connCh), Equals, true)699 c.Check(takeNextBool(cli.connCh), Equals, true)
671 // the connectivity endpoint retried until connected700 // the connectivity endpoint retried until connected
672 c.Check(cCond.OK(), Equals, true)701 c.Check(cCond.OK(), Equals, true)
@@ -690,21 +719,6 @@
690}719}
691720
692/*****************************************************************721/*****************************************************************
693 handleErr tests
694******************************************************************/
695
696func (cs *clientSuite) TestHandleErr(c *C) {
697 cli := NewPushClient(cs.configPath, cs.leveldbPath)
698 cli.log = cs.log
699 cli.systemImageInfo = siInfoRes
700 c.Assert(cli.initSessionAndPoller(), IsNil)
701 cs.log.ResetCapture()
702 cli.hasConnectivity = true
703 cli.handleErr(errors.New("bananas"))
704 c.Check(cs.log.Captured(), Matches, ".*session exited.*bananas\n")
705}
706
707/*****************************************************************
708 seenStateFactory tests722 seenStateFactory tests
709******************************************************************/723******************************************************************/
710724
@@ -712,6 +726,7 @@
712 cli := NewPushClient(cs.configPath, "")726 cli := NewPushClient(cs.configPath, "")
713 ln, err := cli.seenStateFactory()727 ln, err := cli.seenStateFactory()
714 c.Assert(err, IsNil)728 c.Assert(err, IsNil)
729 defer ln.Close()
715 c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.memSeenState")730 c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.memSeenState")
716}731}
717732
@@ -719,63 +734,11 @@
719 cli := NewPushClient(cs.configPath, ":memory:")734 cli := NewPushClient(cs.configPath, ":memory:")
720 ln, err := cli.seenStateFactory()735 ln, err := cli.seenStateFactory()
721 c.Assert(err, IsNil)736 c.Assert(err, IsNil)
737 defer ln.Close()
722 c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.sqliteSeenState")738 c.Check(fmt.Sprintf("%T", ln), Equals, "*seenstate.sqliteSeenState")
723}739}
724740
725/*****************************************************************741/*****************************************************************
726 handleConnState tests
727******************************************************************/
728
729func (cs *clientSuite) TestHandleConnStateD2C(c *C) {
730 cli := NewPushClient(cs.configPath, cs.leveldbPath)
731 cli.log = cs.log
732 cli.systemImageInfo = siInfoRes
733 c.Assert(cli.initSessionAndPoller(), IsNil)
734
735 c.Assert(cli.hasConnectivity, Equals, false)
736 cli.handleConnState(true)
737 c.Check(cli.hasConnectivity, Equals, true)
738 c.Assert(cli.session, NotNil)
739}
740
741func (cs *clientSuite) TestHandleConnStateSame(c *C) {
742 cli := NewPushClient(cs.configPath, cs.leveldbPath)
743 cli.log = cs.log
744 // here we want to check that we don't do anything
745 c.Assert(cli.session, IsNil)
746 c.Assert(cli.hasConnectivity, Equals, false)
747 cli.handleConnState(false)
748 c.Check(cli.session, IsNil)
749
750 cli.hasConnectivity = true
751 cli.handleConnState(true)
752 c.Check(cli.session, IsNil)
753}
754
755func (cs *clientSuite) TestHandleConnStateC2D(c *C) {
756 cli := NewPushClient(cs.configPath, cs.leveldbPath)
757 cli.log = cs.log
758 cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log)
759 cli.session.Dial()
760 cli.hasConnectivity = true
761
762 // cli.session.State() will be "Error" here, for now at least
763 c.Check(cli.session.State(), Not(Equals), session.Disconnected)
764 cli.handleConnState(false)
765 c.Check(cli.session.State(), Equals, session.Disconnected)
766}
767
768func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) {
769 cli := NewPushClient(cs.configPath, cs.leveldbPath)
770 cli.log = cs.log
771 cli.session, _ = session.NewSession(cli.config.Addr, cli.deriveSessionConfig(nil), cli.deviceId, seenstate.NewSeenState, cs.log)
772 cli.hasConnectivity = true
773
774 cli.handleConnState(false)
775 c.Check(cli.session.State(), Equals, session.Disconnected)
776}
777
778/*****************************************************************
779 filterBroadcastNotification tests742 filterBroadcastNotification tests
780******************************************************************/743******************************************************************/
781744
@@ -993,7 +956,6 @@
993var nopConn = func(bool) {}956var nopConn = func(bool) {}
994var nopBcast = func(*session.BroadcastNotification) error { return nil }957var nopBcast = func(*session.BroadcastNotification) error { return nil }
995var nopUcast = func(session.AddressedNotification) error { return nil }958var nopUcast = func(session.AddressedNotification) error { return nil }
996var nopError = func(error) {}
997var nopUnregister = func(*click.AppId) {}959var nopUnregister = func(*click.AppId) {}
998var nopAcct = func() {}960var nopAcct = func() {}
999961
@@ -1006,7 +968,7 @@
1006 c.Assert(cli.initSessionAndPoller(), IsNil)968 c.Assert(cli.initSessionAndPoller(), IsNil)
1007969
1008 ch := make(chan bool, 1)970 ch := make(chan bool, 1)
1009 go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopError, nopUnregister, nopAcct)971 go cli.doLoop(func(bool) { ch <- true }, nopBcast, nopUcast, nopUnregister, nopAcct)
1010 c.Check(takeNextBool(ch), Equals, true)972 c.Check(takeNextBool(ch), Equals, true)
1011}973}
1012974
@@ -1015,11 +977,11 @@
1015 cli.log = cs.log977 cli.log = cs.log
1016 cli.systemImageInfo = siInfoRes978 cli.systemImageInfo = siInfoRes
1017 c.Assert(cli.initSessionAndPoller(), IsNil)979 c.Assert(cli.initSessionAndPoller(), IsNil)
1018 cli.session.BroadcastCh = make(chan *session.BroadcastNotification, 1)980 cli.broadcastCh = make(chan *session.BroadcastNotification, 1)
1019 cli.session.BroadcastCh <- &session.BroadcastNotification{}981 cli.broadcastCh <- &session.BroadcastNotification{}
1020982
1021 ch := make(chan bool, 1)983 ch := make(chan bool, 1)
1022 go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopError, nopUnregister, nopAcct)984 go cli.doLoop(nopConn, func(_ *session.BroadcastNotification) error { ch <- true; return nil }, nopUcast, nopUnregister, nopAcct)
1023 c.Check(takeNextBool(ch), Equals, true)985 c.Check(takeNextBool(ch), Equals, true)
1024}986}
1025987
@@ -1028,24 +990,11 @@
1028 cli.log = cs.log990 cli.log = cs.log
1029 cli.systemImageInfo = siInfoRes991 cli.systemImageInfo = siInfoRes
1030 c.Assert(cli.initSessionAndPoller(), IsNil)992 c.Assert(cli.initSessionAndPoller(), IsNil)
1031 cli.session.NotificationsCh = make(chan session.AddressedNotification, 1)993 cli.notificationsCh = make(chan session.AddressedNotification, 1)
1032 cli.session.NotificationsCh <- session.AddressedNotification{}994 cli.notificationsCh <- session.AddressedNotification{}
1033995
1034 ch := make(chan bool, 1)996 ch := make(chan bool, 1)
1035 go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopError, nopUnregister, nopAcct)997 go cli.doLoop(nopConn, nopBcast, func(session.AddressedNotification) error { ch <- true; return nil }, nopUnregister, nopAcct)
1036 c.Check(takeNextBool(ch), Equals, true)
1037}
1038
1039func (cs *clientSuite) TestDoLoopErr(c *C) {
1040 cli := NewPushClient(cs.configPath, cs.leveldbPath)
1041 cli.log = cs.log
1042 cli.systemImageInfo = siInfoRes
1043 c.Assert(cli.initSessionAndPoller(), IsNil)
1044 cli.session.ErrCh = make(chan error, 1)
1045 cli.session.ErrCh <- nil
1046
1047 ch := make(chan bool, 1)
1048 go cli.doLoop(nopConn, nopBcast, nopUcast, func(error) { ch <- true }, nopUnregister, nopAcct)
1049 c.Check(takeNextBool(ch), Equals, true)998 c.Check(takeNextBool(ch), Equals, true)
1050}999}
10511000
@@ -1058,7 +1007,7 @@
1058 cli.unregisterCh <- app11007 cli.unregisterCh <- app1
10591008
1060 ch := make(chan bool, 1)1009 ch := make(chan bool, 1)
1061 go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct)1010 go cli.doLoop(nopConn, nopBcast, nopUcast, func(app *click.AppId) { c.Check(app.Original(), Equals, appId1); ch <- true }, nopAcct)
1062 c.Check(takeNextBool(ch), Equals, true)1011 c.Check(takeNextBool(ch), Equals, true)
1063}1012}
10641013
@@ -1072,7 +1021,7 @@
1072 cli.accountsCh = acctCh1021 cli.accountsCh = acctCh
10731022
1074 ch := make(chan bool, 1)1023 ch := make(chan bool, 1)
1075 go cli.doLoop(nopConn, nopBcast, nopUcast, nopError, nopUnregister, func() { ch <- true })1024 go cli.doLoop(nopConn, nopBcast, nopUcast, nopUnregister, func() { ch <- true })
1076 c.Check(takeNextBool(ch), Equals, true)1025 c.Check(takeNextBool(ch), Equals, true)
1077}1026}
10781027
@@ -1107,6 +1056,20 @@
1107 Loop() tests1056 Loop() tests
1108******************************************************************/1057******************************************************************/
11091058
1059type loopSession struct{ hasConn bool }
1060
1061func (s *loopSession) ResetCookie() {}
1062func (s *loopSession) State() session.ClientSessionState {
1063 if s.hasConn {
1064 return session.Connected
1065 } else {
1066 return session.Disconnected
1067 }
1068}
1069func (s *loopSession) HasConnectivity(hasConn bool) { s.hasConn = hasConn }
1070func (s *loopSession) KeepConnection() error { return nil }
1071func (s *loopSession) StopKeepConnection() {}
1072
1110func (cs *clientSuite) TestLoop(c *C) {1073func (cs *clientSuite) TestLoop(c *C) {
1111 cli := NewPushClient(cs.configPath, cs.leveldbPath)1074 cli := NewPushClient(cs.configPath, cs.leveldbPath)
1112 cli.connCh = make(chan bool)1075 cli.connCh = make(chan bool)
@@ -1121,8 +1084,7 @@
11211084
1122 c.Assert(cli.initSessionAndPoller(), IsNil)1085 c.Assert(cli.initSessionAndPoller(), IsNil)
11231086
1124 cli.session.BroadcastCh = make(chan *session.BroadcastNotification)1087 cli.broadcastCh = make(chan *session.BroadcastNotification)
1125 cli.session.ErrCh = make(chan error)
11261088
1127 // we use tick() to make sure things have been through the1089 // we use tick() to make sure things have been through the
1128 // event loop at least once before looking at things;1090 // event loop at least once before looking at things;
@@ -1130,6 +1092,10 @@
1130 // at and the loop itself.1092 // at and the loop itself.
1131 tick := func() { cli.sessionConnectedCh <- 42 }1093 tick := func() { cli.sessionConnectedCh <- 42 }
11321094
1095 c.Assert(cli.session, NotNil)
1096 cli.session.StopKeepConnection()
1097 cli.session = &loopSession{}
1098
1133 go cli.Loop()1099 go cli.Loop()
11341100
1135 // sessionConnectedCh to nothing in particular, but it'll help sync this test1101 // sessionConnectedCh to nothing in particular, but it'll help sync this test
@@ -1139,24 +1105,19 @@
11391105
1140 // loop() should have connected:1106 // loop() should have connected:
1141 // * connCh to the connectivity checker1107 // * connCh to the connectivity checker
1142 c.Check(cli.hasConnectivity, Equals, false)1108 c.Check(cli.session.State(), Equals, session.Disconnected)
1143 cli.connCh <- true1109 cli.connCh <- true
1144 tick()1110 tick()
1145 c.Check(cli.hasConnectivity, Equals, true)1111 c.Check(cli.session.State(), Equals, session.Connected)
1146 cli.connCh <- false1112 cli.connCh <- false
1147 tick()1113 tick()
1148 c.Check(cli.hasConnectivity, Equals, false)1114 c.Check(cli.session.State(), Equals, session.Disconnected)
11491115
1150 // * session.BroadcastCh to the notifications handler1116 // * session.BroadcastCh to the notifications handler
1151 c.Check(d.bcastCount, Equals, 0)1117 c.Check(d.bcastCount, Equals, 0)
1152 cli.session.BroadcastCh <- positiveBroadcastNotification1118 cli.broadcastCh <- positiveBroadcastNotification
1153 tick()1119 tick()
1154 c.Check(d.bcastCount, Equals, 1)1120 c.Check(d.bcastCount, Equals, 1)
1155
1156 // * session.ErrCh to the error handler
1157 cli.session.ErrCh <- nil
1158 tick()
1159 c.Check(cs.log.Captured(), Matches, "(?ms).*session exited.*")
1160}1121}
11611122
1162/*****************************************************************1123/*****************************************************************
11631124
=== modified file 'client/service/postal.go'
--- client/service/postal.go 2015-01-22 17:34:18 +0000
+++ client/service/postal.go 2015-03-26 16:42:21 +0000
@@ -24,6 +24,7 @@
24 "code.google.com/p/go-uuid/uuid"24 "code.google.com/p/go-uuid/uuid"
2525
26 "launchpad.net/ubuntu-push/bus"26 "launchpad.net/ubuntu-push/bus"
27 "launchpad.net/ubuntu-push/bus/accounts"
27 "launchpad.net/ubuntu-push/bus/emblemcounter"28 "launchpad.net/ubuntu-push/bus/emblemcounter"
28 "launchpad.net/ubuntu-push/bus/haptic"29 "launchpad.net/ubuntu-push/bus/haptic"
29 "launchpad.net/ubuntu-push/bus/notifications"30 "launchpad.net/ubuntu-push/bus/notifications"
@@ -75,6 +76,7 @@
75 // the endpoints are only exposed for testing from client76 // the endpoints are only exposed for testing from client
76 // XXX: uncouple some more so this isn't necessary77 // XXX: uncouple some more so this isn't necessary
77 EmblemCounterEndp bus.Endpoint78 EmblemCounterEndp bus.Endpoint
79 AccountsEndp bus.Endpoint
78 HapticEndp bus.Endpoint80 HapticEndp bus.Endpoint
79 NotificationsEndp bus.Endpoint81 NotificationsEndp bus.Endpoint
80 UnityGreeterEndp bus.Endpoint82 UnityGreeterEndp bus.Endpoint
@@ -82,6 +84,7 @@
82 // presenters:84 // presenters:
83 Presenters []Presenter85 Presenters []Presenter
84 emblemCounter *emblemcounter.EmblemCounter86 emblemCounter *emblemcounter.EmblemCounter
87 accounts accounts.Accounts
85 haptic *haptic.Haptic88 haptic *haptic.Haptic
86 notifications *notifications.RawNotifications89 notifications *notifications.RawNotifications
87 sound *sounds.Sound90 sound *sounds.Sound
@@ -117,6 +120,7 @@
117 svc.fallbackSound = setup.FallbackSound120 svc.fallbackSound = setup.FallbackSound
118 svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log)121 svc.NotificationsEndp = bus.SessionBus.Endpoint(notifications.BusAddress, log)
119 svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log)122 svc.EmblemCounterEndp = bus.SessionBus.Endpoint(emblemcounter.BusAddress, log)
123 svc.AccountsEndp = bus.SystemBus.Endpoint(accounts.BusAddress, log)
120 svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log)124 svc.HapticEndp = bus.SessionBus.Endpoint(haptic.BusAddress, log)
121 svc.UnityGreeterEndp = bus.SessionBus.Endpoint(unitygreeter.BusAddress, log)125 svc.UnityGreeterEndp = bus.SessionBus.Endpoint(unitygreeter.BusAddress, log)
122 svc.WindowStackEndp = bus.SessionBus.Endpoint(windowstack.BusAddress, log)126 svc.WindowStackEndp = bus.SessionBus.Endpoint(windowstack.BusAddress, log)
@@ -158,8 +162,13 @@
158 svc.urlDispatcher = urldispatcher.New(svc.Log)162 svc.urlDispatcher = urldispatcher.New(svc.Log)
159 svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log)163 svc.notifications = notifications.Raw(svc.NotificationsEndp, svc.Log)
160 svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log)164 svc.emblemCounter = emblemcounter.New(svc.EmblemCounterEndp, svc.Log)
161 svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.fallbackVibration)165 svc.accounts = accounts.New(svc.AccountsEndp, svc.Log)
162 svc.sound = sounds.New(svc.Log, svc.fallbackSound)166 err = svc.accounts.Start()
167 if err != nil {
168 return err
169 }
170 svc.haptic = haptic.New(svc.HapticEndp, svc.Log, svc.accounts, svc.fallbackVibration)
171 svc.sound = sounds.New(svc.Log, svc.accounts, svc.fallbackSound)
163 svc.messagingMenu = messaging.New(svc.Log)172 svc.messagingMenu = messaging.New(svc.Log)
164 svc.Presenters = []Presenter{173 svc.Presenters = []Presenter{
165 svc.notifications,174 svc.notifications,
@@ -228,6 +237,7 @@
228 }{237 }{
229 {"notifications", svc.NotificationsEndp},238 {"notifications", svc.NotificationsEndp},
230 {"emblemcounter", svc.EmblemCounterEndp},239 {"emblemcounter", svc.EmblemCounterEndp},
240 {"accounts", svc.AccountsEndp},
231 {"haptic", svc.HapticEndp},241 {"haptic", svc.HapticEndp},
232 {"unitygreeter", svc.UnityGreeterEndp},242 {"unitygreeter", svc.UnityGreeterEndp},
233 {"windowstack", svc.WindowStackEndp},243 {"windowstack", svc.WindowStackEndp},
234244
=== modified file 'client/service/postal_test.go'
--- client/service/postal_test.go 2014-09-09 22:54:04 +0000
+++ client/service/postal_test.go 2015-03-26 16:42:21 +0000
@@ -169,6 +169,8 @@
169 hapticBus bus.Endpoint169 hapticBus bus.Endpoint
170 unityGreeterBus bus.Endpoint170 unityGreeterBus bus.Endpoint
171 winStackBus bus.Endpoint171 winStackBus bus.Endpoint
172 accountsBus bus.Endpoint
173 accountsCh chan []interface{}
172 fakeLauncher *fakeHelperLauncher174 fakeLauncher *fakeHelperLauncher
173 getTempDir func(string) (string, error)175 getTempDir func(string) (string, error)
174 oldIsBlisted func(*click.AppId) bool176 oldIsBlisted func(*click.AppId) bool
@@ -194,6 +196,7 @@
194 ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))196 ps.bus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
195 ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))197 ps.notifBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
196 ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))198 ps.counterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
199 ps.accountsBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), map[string]dbus.Variant{"IncomingMessageVibrate": dbus.Variant{true}})
197 ps.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))200 ps.hapticBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true))
198 ps.unityGreeterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), false)201 ps.unityGreeterBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), false)
199 ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{})202 ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{})
@@ -206,11 +209,15 @@
206 tmpDir := filepath.Join(d, pkgName)209 tmpDir := filepath.Join(d, pkgName)
207 return tmpDir, os.MkdirAll(tmpDir, 0700)210 return tmpDir, os.MkdirAll(tmpDir, 0700)
208 }211 }
212
213 ps.accountsCh = make(chan []interface{})
214 testibus.SetWatchSource(ps.accountsBus, "PropertiesChanged", ps.accountsCh)
209}215}
210216
211func (ps *postalSuite) TearDownTest(c *C) {217func (ps *postalSuite) TearDownTest(c *C) {
212 isBlacklisted = ps.oldIsBlisted218 isBlacklisted = ps.oldIsBlisted
213 launch_helper.GetTempDir = ps.getTempDir219 launch_helper.GetTempDir = ps.getTempDir
220 close(ps.accountsCh)
214}221}
215222
216func (ts *trivialPostalSuite) SetUpTest(c *C) {223func (ts *trivialPostalSuite) SetUpTest(c *C) {
@@ -227,6 +234,7 @@
227 pst.Bus = ps.bus234 pst.Bus = ps.bus
228 pst.NotificationsEndp = ps.notifBus235 pst.NotificationsEndp = ps.notifBus
229 pst.EmblemCounterEndp = ps.counterBus236 pst.EmblemCounterEndp = ps.counterBus
237 pst.AccountsEndp = ps.accountsBus
230 pst.HapticEndp = ps.hapticBus238 pst.HapticEndp = ps.hapticBus
231 pst.UnityGreeterEndp = ps.unityGreeterBus239 pst.UnityGreeterEndp = ps.unityGreeterBus
232 pst.WindowStackEndp = ps.winStackBus240 pst.WindowStackEndp = ps.winStackBus
@@ -544,6 +552,7 @@
544 svc := NewPostalService(ps.cfg, ps.log)552 svc := NewPostalService(ps.cfg, ps.log)
545 svc.Bus = endp553 svc.Bus = endp
546 svc.EmblemCounterEndp = endp554 svc.EmblemCounterEndp = endp
555 svc.AccountsEndp = ps.accountsBus
547 svc.HapticEndp = endp556 svc.HapticEndp = endp
548 svc.NotificationsEndp = endp557 svc.NotificationsEndp = endp
549 svc.UnityGreeterEndp = ps.unityGreeterBus558 svc.UnityGreeterEndp = ps.unityGreeterBus
@@ -552,6 +561,10 @@
552 svc.fallbackVibration = &launch_helper.Vibration{Pattern: []uint32{1}}561 svc.fallbackVibration = &launch_helper.Vibration{Pattern: []uint32{1}}
553 c.Assert(svc.Start(), IsNil)562 c.Assert(svc.Start(), IsNil)
554563
564 nopTicker := make(chan []interface{})
565 testibus.SetWatchSource(endp, "ActionInvoked", nopTicker)
566 defer close(nopTicker)
567
555 // Persist is false so we just check the log568 // Persist is false so we just check the log
556 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false}569 card := &launch_helper.Card{Icon: "icon-value", Summary: "summary-value", Body: "body-value", Popup: true, Persist: false}
557 vib := json.RawMessage(`true`)570 vib := json.RawMessage(`true`)
@@ -837,6 +850,10 @@
837}850}
838851
839func (ps *postalSuite) TestBlacklisted(c *C) {852func (ps *postalSuite) TestBlacklisted(c *C) {
853 ps.winStackBus = testibus.NewTestingEndpoint(condition.Work(true), condition.Work(true), []windowstack.WindowsInfo{},
854 []windowstack.WindowsInfo{},
855 []windowstack.WindowsInfo{},
856 []windowstack.WindowsInfo{})
840 svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))857 svc := ps.replaceBuses(NewPostalService(ps.cfg, ps.log))
841 svc.Start()858 svc.Start()
842 ps.blacklisted = false859 ps.blacklisted = false
843860
=== modified file 'client/service/service.go'
--- client/service/service.go 2015-01-22 17:34:18 +0000
+++ client/service/service.go 2015-03-26 16:42:21 +0000
@@ -140,6 +140,8 @@
140 case resp.StatusCode >= http.StatusInternalServerError:140 case resp.StatusCode >= http.StatusInternalServerError:
141 // XXX retry on 503141 // XXX retry on 503
142 return nil, ErrBadServer142 return nil, ErrBadServer
143 case resp.StatusCode == http.StatusUnauthorized:
144 return nil, ErrBadAuth
143 default:145 default:
144 return nil, ErrBadRequest146 return nil, ErrBadRequest
145 }147 }
146148
=== modified file 'client/service/service_test.go'
--- client/service/service_test.go 2014-08-06 09:01:59 +0000
+++ client/service/service_test.go 2015-03-26 16:42:21 +0000
@@ -19,6 +19,7 @@
19import (19import (
20 "encoding/json"20 "encoding/json"
21 "fmt"21 "fmt"
22 "io"
22 "net/http"23 "net/http"
23 "net/http/httptest"24 "net/http/httptest"
24 "os"25 "os"
@@ -179,7 +180,8 @@
179func (ss *serviceSuite) TestRegistrationWorks(c *C) {180func (ss *serviceSuite) TestRegistrationWorks(c *C) {
180 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {181 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181 buf := make([]byte, 256)182 buf := make([]byte, 256)
182 n, e := r.Body.Read(buf)183 n := r.ContentLength
184 _, e := io.ReadFull(r.Body, buf[:n])
183 c.Assert(e, IsNil)185 c.Assert(e, IsNil)
184 req := registrationRequest{}186 req := registrationRequest{}
185 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)187 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
@@ -240,6 +242,23 @@
240 c.Check(err, ErrorMatches, "unable to request registration: .*")242 c.Check(err, ErrorMatches, "unable to request registration: .*")
241}243}
242244
245func (ss *serviceSuite) TestManageRegFailsOn401(c *C) {
246 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
247 http.Error(w, "Unauthorized", 401)
248 }))
249 defer ts.Close()
250 setup := &PushServiceSetup{
251 DeviceId: "fake-device-id",
252 RegURL: helpers.ParseURL(ts.URL),
253 AuthGetter: func(string) string { return "tok" },
254 }
255 svc := NewPushService(setup, ss.log)
256 svc.Bus = ss.bus
257 reg, err := svc.register(aPackageOnBus, []interface{}{anAppId}, nil)
258 c.Check(err, Equals, ErrBadAuth)
259 c.Check(reg, IsNil)
260}
261
243func (ss *serviceSuite) TestManageRegFailsOn40x(c *C) {262func (ss *serviceSuite) TestManageRegFailsOn40x(c *C) {
244 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {263 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
245 http.Error(w, "I'm a teapot", 418)264 http.Error(w, "I'm a teapot", 418)
@@ -277,7 +296,8 @@
277func (ss *serviceSuite) TestManageRegFailsOnBadJSON(c *C) {296func (ss *serviceSuite) TestManageRegFailsOnBadJSON(c *C) {
278 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {297 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
279 buf := make([]byte, 256)298 buf := make([]byte, 256)
280 n, e := r.Body.Read(buf)299 n := r.ContentLength
300 _, e := io.ReadFull(r.Body, buf[:n])
281 c.Assert(e, IsNil)301 c.Assert(e, IsNil)
282 req := registrationRequest{}302 req := registrationRequest{}
283 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)303 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
@@ -303,7 +323,8 @@
303func (ss *serviceSuite) TestManageRegFailsOnBadJSONDocument(c *C) {323func (ss *serviceSuite) TestManageRegFailsOnBadJSONDocument(c *C) {
304 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {324 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
305 buf := make([]byte, 256)325 buf := make([]byte, 256)
306 n, e := r.Body.Read(buf)326 n := r.ContentLength
327 _, e := io.ReadFull(r.Body, buf[:n])
307 c.Assert(e, IsNil)328 c.Assert(e, IsNil)
308 req := registrationRequest{}329 req := registrationRequest{}
309 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)330 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
@@ -329,7 +350,8 @@
329func (ss *serviceSuite) TestDBusUnregisterWorks(c *C) {350func (ss *serviceSuite) TestDBusUnregisterWorks(c *C) {
330 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {351 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
331 buf := make([]byte, 256)352 buf := make([]byte, 256)
332 n, e := r.Body.Read(buf)353 n := r.ContentLength
354 _, e := io.ReadFull(r.Body, buf[:n])
333 c.Assert(e, IsNil)355 c.Assert(e, IsNil)
334 req := registrationRequest{}356 req := registrationRequest{}
335 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)357 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
@@ -356,7 +378,8 @@
356 invoked := make(chan bool, 1)378 invoked := make(chan bool, 1)
357 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {379 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
358 buf := make([]byte, 256)380 buf := make([]byte, 256)
359 n, e := r.Body.Read(buf)381 n := r.ContentLength
382 _, e := io.ReadFull(r.Body, buf[:n])
360 c.Assert(e, IsNil)383 c.Assert(e, IsNil)
361 req := registrationRequest{}384 req := registrationRequest{}
362 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)385 c.Assert(json.Unmarshal(buf[:n], &req), IsNil)
363386
=== modified file 'client/session/seenstate/seenstate.go'
--- client/session/seenstate/seenstate.go 2014-05-14 17:42:24 +0000
+++ client/session/seenstate/seenstate.go 2015-03-26 16:42:21 +0000
@@ -28,8 +28,10 @@
28 // GetAll() returns a "simple" map of the current levels.28 // GetAll() returns a "simple" map of the current levels.
29 GetAllLevels() (map[string]int64, error)29 GetAllLevels() (map[string]int64, error)
30 // FilterBySeen filters notifications already seen, keep track30 // FilterBySeen filters notifications already seen, keep track
31 // of them as well31 // of them as well.
32 FilterBySeen([]protocol.Notification) ([]protocol.Notification, error)32 FilterBySeen([]protocol.Notification) ([]protocol.Notification, error)
33 // Close closes state.
34 Close()
33}35}
3436
35type memSeenState struct {37type memSeenState struct {
@@ -58,6 +60,9 @@
58 return acc, nil60 return acc, nil
59}61}
6062
63func (m *memSeenState) Close() {
64}
65
61var _ SeenState = (*memSeenState)(nil)66var _ SeenState = (*memSeenState)(nil)
6267
63// NewSeenState returns an implementation of SeenState that is memory-based and68// NewSeenState returns an implementation of SeenState that is memory-based and
6469
=== modified file 'client/session/seenstate/sqlseenstate.go'
--- client/session/seenstate/sqlseenstate.go 2014-05-14 17:42:24 +0000
+++ client/session/seenstate/sqlseenstate.go 2015-03-26 16:42:21 +0000
@@ -48,6 +48,11 @@
48 return &sqliteSeenState{db}, nil48 return &sqliteSeenState{db}, nil
49}49}
5050
51// Closes closes the underlying db.
52func (ps *sqliteSeenState) Close() {
53 ps.db.Close()
54}
55
51func (ps *sqliteSeenState) SetLevel(level string, top int64) error {56func (ps *sqliteSeenState) SetLevel(level string, top int64) error {
52 _, err := ps.db.Exec("REPLACE INTO level_map (level, top) VALUES (?, ?)", level, top)57 _, err := ps.db.Exec("REPLACE INTO level_map (level, top) VALUES (?, ?)", level, top)
53 if err != nil {58 if err != nil {
5459
=== modified file 'client/session/seenstate/sqlseenstate_test.go'
--- client/session/seenstate/sqlseenstate_test.go 2014-05-14 17:42:24 +0000
+++ client/session/seenstate/sqlseenstate_test.go 2015-03-26 16:42:21 +0000
@@ -112,6 +112,15 @@
112 c.Check(err, ErrorMatches, "cannot insert .*")112 c.Check(err, ErrorMatches, "cannot insert .*")
113}113}
114114
115func (s *sqlsSuite) TestClose(c *C) {
116 dir := c.MkDir()
117 filename := dir + "test.db"
118 sqls, err := NewSqliteSeenState(filename)
119 c.Check(err, IsNil)
120 c.Assert(sqls, NotNil)
121 sqls.Close()
122}
123
115func (s *sqlsSuite) TestDropPrevThan(c *C) {124func (s *sqlsSuite) TestDropPrevThan(c *C) {
116 dir := c.MkDir()125 dir := c.MkDir()
117 filename := dir + "test.db"126 filename := dir + "test.db"
118127
=== modified file 'client/session/session.go'
--- client/session/session.go 2015-01-22 17:34:18 +0000
+++ client/session/session.go 2015-03-26 16:42:21 +0000
@@ -39,6 +39,14 @@
39 "launchpad.net/ubuntu-push/util"39 "launchpad.net/ubuntu-push/util"
40)40)
4141
42type sessCmd uint8
43
44const (
45 cmdDisconnect sessCmd = iota
46 cmdConnect
47 cmdResetCookie
48)
49
42var (50var (
43 wireVersionBytes = []byte{protocol.ProtocolWireVersion}51 wireVersionBytes = []byte{protocol.ProtocolWireVersion}
44)52)
@@ -70,10 +78,12 @@
7078
71const (79const (
72 Error ClientSessionState = iota80 Error ClientSessionState = iota
81 Pristine
73 Disconnected82 Disconnected
74 Connected83 Connected
75 Started84 Started
76 Running85 Running
86 Shutdown
77 Unknown87 Unknown
78)88)
7989
@@ -83,10 +93,12 @@
83 }93 }
84 return [Unknown]string{94 return [Unknown]string{
85 "Error",95 "Error",
96 "Pristine",
86 "Disconnected",97 "Disconnected",
87 "Connected",98 "Connected",
88 "Started",99 "Started",
89 "Running",100 "Running",
101 "Shutdown",
90 }[s]102 }[s]
91}103}
92104
@@ -118,10 +130,20 @@
118 AuthGetter func(string) string130 AuthGetter func(string) string
119 AuthURL string131 AuthURL string
120 AddresseeChecker AddresseeChecking132 AddresseeChecker AddresseeChecking
133 BroadcastCh chan *BroadcastNotification
134 NotificationsCh chan AddressedNotification
121}135}
122136
123// ClientSession holds a client<->server session and its configuration.137// ClientSession holds a client<->server session and its configuration.
124type ClientSession struct {138type ClientSession interface {
139 ResetCookie()
140 State() ClientSessionState
141 HasConnectivity(bool)
142 KeepConnection() error
143 StopKeepConnection()
144}
145
146type clientSession struct {
125 // configuration147 // configuration
126 DeviceId string148 DeviceId string
127 ClientSessionConfig149 ClientSessionConfig
@@ -145,25 +167,36 @@
145 proto protocol.Protocol167 proto protocol.Protocol
146 pingInterval time.Duration168 pingInterval time.Duration
147 retrier util.AutoRedialer169 retrier util.AutoRedialer
148 retrierLock sync.Mutex
149 cookie string170 cookie string
150 // status171 // status
151 stateP *uint32172 stateLock sync.RWMutex
152 ErrCh chan error173 state ClientSessionState
153 BroadcastCh chan *BroadcastNotification
154 NotificationsCh chan AddressedNotification
155 // authorization174 // authorization
156 auth string175 auth string
157 // autoredial knobs176 // autoredial knobs
158 shouldDelayP *uint32177 shouldDelayP *uint32
159 lastAutoRedial time.Time178 lastAutoRedial time.Time
160 redialDelay func(*ClientSession) time.Duration179 redialDelay func(*clientSession) time.Duration
161 redialJitter func(time.Duration) time.Duration180 redialJitter func(time.Duration) time.Duration
162 redialDelays []time.Duration181 redialDelays []time.Duration
163 redialDelaysIdx int182 redialDelaysIdx int
183 // connection events, and cookie reset requests, come in over here
184 cmdCh chan sessCmd
185 // last seen connection event is here
186 lastConn bool
187 // connection events are handled by this
188 connHandler func(bool)
189 // autoredial goes over here (xxx spurious goroutine involved)
190 doneCh chan uint32
191 // main loop errors out through here (possibly another spurious goroutine)
192 errCh chan error
193 // main loop errors are handled by this
194 errHandler func(error)
195 // look, a stopper!
196 stopCh chan struct{}
164}197}
165198
166func redialDelay(sess *ClientSession) time.Duration {199func redialDelay(sess *clientSession) time.Duration {
167 if sess.ShouldDelay() {200 if sess.ShouldDelay() {
168 t := sess.redialDelays[sess.redialDelaysIdx]201 t := sess.redialDelays[sess.redialDelaysIdx]
169 if len(sess.redialDelays) > sess.redialDelaysIdx+1 {202 if len(sess.redialDelays) > sess.redialDelaysIdx+1 {
@@ -178,8 +211,7 @@
178211
179func NewSession(serverAddrSpec string, conf ClientSessionConfig,212func NewSession(serverAddrSpec string, conf ClientSessionConfig,
180 deviceId string, seenStateFactory func() (seenstate.SeenState, error),213 deviceId string, seenStateFactory func() (seenstate.SeenState, error),
181 log logger.Logger) (*ClientSession, error) {214 log logger.Logger) (*clientSession, error) {
182 state := uint32(Disconnected)
183 seenState, err := seenStateFactory()215 seenState, err := seenStateFactory()
184 if err != nil {216 if err != nil {
185 return nil, err217 return nil, err
@@ -191,7 +223,7 @@
191 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)223 getHost = gethosts.New(deviceId, hostsEndpoint, conf.ExchangeTimeout)
192 }224 }
193 var shouldDelay uint32 = 0225 var shouldDelay uint32 = 0
194 sess := &ClientSession{226 sess := &clientSession{
195 ClientSessionConfig: conf,227 ClientSessionConfig: conf,
196 getHost: getHost,228 getHost: getHost,
197 fallbackHosts: fallbackHosts,229 fallbackHosts: fallbackHosts,
@@ -200,10 +232,10 @@
200 Protocolator: protocol.NewProtocol0,232 Protocolator: protocol.NewProtocol0,
201 SeenState: seenState,233 SeenState: seenState,
202 TLS: &tls.Config{},234 TLS: &tls.Config{},
203 stateP: &state,235 state: Pristine,
204 timeSince: time.Since,236 timeSince: time.Since,
205 shouldDelayP: &shouldDelay,237 shouldDelayP: &shouldDelay,
206 redialDelay: redialDelay,238 redialDelay: redialDelay, // NOTE there are tests that use calling sess.redialDelay as an indication of calling autoRedial!
207 redialDelays: util.Timeouts(),239 redialDelays: util.Timeouts(),
208 }240 }
209 sess.redialJitter = sess.Jitter241 sess.redialJitter = sess.Jitter
@@ -215,62 +247,78 @@
215 }247 }
216 sess.TLS.RootCAs = cp248 sess.TLS.RootCAs = cp
217 }249 }
250 sess.doneCh = make(chan uint32, 1)
251 sess.stopCh = make(chan struct{})
252 sess.cmdCh = make(chan sessCmd)
253 sess.errCh = make(chan error, 1)
254
255 // to be overridden by tests
256 sess.connHandler = sess.handleConn
257 sess.errHandler = sess.handleErr
258
218 return sess, nil259 return sess, nil
219}260}
220261
221func (sess *ClientSession) ShouldDelay() bool {262func (sess *clientSession) ShouldDelay() bool {
222 return atomic.LoadUint32(sess.shouldDelayP) != 0263 return atomic.LoadUint32(sess.shouldDelayP) != 0
223}264}
224265
225func (sess *ClientSession) setShouldDelay() {266func (sess *clientSession) setShouldDelay() {
226 atomic.StoreUint32(sess.shouldDelayP, uint32(1))267 atomic.StoreUint32(sess.shouldDelayP, uint32(1))
227}268}
228269
229func (sess *ClientSession) clearShouldDelay() {270func (sess *clientSession) clearShouldDelay() {
230 atomic.StoreUint32(sess.shouldDelayP, uint32(0))271 atomic.StoreUint32(sess.shouldDelayP, uint32(0))
231}272}
232273
233func (sess *ClientSession) State() ClientSessionState {274func (sess *clientSession) State() ClientSessionState {
234 return ClientSessionState(atomic.LoadUint32(sess.stateP))275 sess.stateLock.RLock()
235}276 defer sess.stateLock.RUnlock()
236277 return sess.state
237func (sess *ClientSession) setState(state ClientSessionState) {278}
238 sess.Log.Debugf("session.setState: %s -> %s", ClientSessionState(atomic.LoadUint32(sess.stateP)), state)279
239 atomic.StoreUint32(sess.stateP, uint32(state))280func (sess *clientSession) setState(state ClientSessionState) {
240}281 sess.stateLock.Lock()
241282 defer sess.stateLock.Unlock()
242func (sess *ClientSession) setConnection(conn net.Conn) {283 sess.Log.Debugf("session.setState: %s -> %s", sess.state, state)
284 sess.state = state
285}
286
287func (sess *clientSession) setConnection(conn net.Conn) {
243 sess.connLock.Lock()288 sess.connLock.Lock()
244 defer sess.connLock.Unlock()289 defer sess.connLock.Unlock()
245 sess.Connection = conn290 sess.Connection = conn
246}291}
247292
248func (sess *ClientSession) getConnection() net.Conn {293func (sess *clientSession) getConnection() net.Conn {
249 sess.connLock.RLock()294 sess.connLock.RLock()
250 defer sess.connLock.RUnlock()295 defer sess.connLock.RUnlock()
251 return sess.Connection296 return sess.Connection
252}297}
253298
254func (sess *ClientSession) setCookie(cookie string) {299func (sess *clientSession) setCookie(cookie string) {
255 sess.connLock.Lock()300 sess.connLock.Lock()
256 defer sess.connLock.Unlock()301 defer sess.connLock.Unlock()
257 sess.cookie = cookie302 sess.cookie = cookie
258}303}
259304
260func (sess *ClientSession) getCookie() string {305func (sess *clientSession) getCookie() string {
261 sess.connLock.RLock()306 sess.connLock.RLock()
262 defer sess.connLock.RUnlock()307 defer sess.connLock.RUnlock()
263 return sess.cookie308 return sess.cookie
264}309}
265310
266func (sess *ClientSession) ClearCookie() {311func (sess *clientSession) ResetCookie() {
267 sess.connLock.Lock()312 sess.cmdCh <- cmdResetCookie
268 defer sess.connLock.Unlock()313}
269 sess.cookie = ""314
315func (sess *clientSession) resetCookie() {
316 sess.stopRedial()
317 sess.doClose(true)
270}318}
271319
272// getHosts sets deliveryHosts possibly querying a remote endpoint320// getHosts sets deliveryHosts possibly querying a remote endpoint
273func (sess *ClientSession) getHosts() error {321func (sess *clientSession) getHosts() error {
274 if sess.getHost != nil {322 if sess.getHost != nil {
275 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {323 if sess.deliveryHosts != nil && sess.timeSince(sess.deliveryHostsTimestamp) < sess.HostsCachingExpiryTime {
276 return nil324 return nil
@@ -294,7 +342,7 @@
294342
295// addAuthorization gets the authorization blob to send to the server343// addAuthorization gets the authorization blob to send to the server
296// and adds it to the session.344// and adds it to the session.
297func (sess *ClientSession) addAuthorization() error {345func (sess *clientSession) addAuthorization() error {
298 if sess.AuthGetter != nil {346 if sess.AuthGetter != nil {
299 sess.Log.Debugf("adding authorization")347 sess.Log.Debugf("adding authorization")
300 sess.auth = sess.AuthGetter(sess.AuthURL)348 sess.auth = sess.AuthGetter(sess.AuthURL)
@@ -302,13 +350,13 @@
302 return nil350 return nil
303}351}
304352
305func (sess *ClientSession) resetHosts() {353func (sess *clientSession) resetHosts() {
306 sess.deliveryHosts = nil354 sess.deliveryHosts = nil
307}355}
308356
309// startConnectionAttempt/nextHostToTry help connect iterating over candidate hosts357// startConnectionAttempt/nextHostToTry help connect iterating over candidate hosts
310358
311func (sess *ClientSession) startConnectionAttempt() {359func (sess *clientSession) startConnectionAttempt() {
312 if sess.timeSince(sess.lastAttemptTimestamp) > sess.ExpectAllRepairedTime {360 if sess.timeSince(sess.lastAttemptTimestamp) > sess.ExpectAllRepairedTime {
313 sess.tryHost = 0361 sess.tryHost = 0
314 }362 }
@@ -319,7 +367,7 @@
319 sess.lastAttemptTimestamp = time.Now()367 sess.lastAttemptTimestamp = time.Now()
320}368}
321369
322func (sess *ClientSession) nextHostToTry() string {370func (sess *clientSession) nextHostToTry() string {
323 if sess.leftToTry == 0 {371 if sess.leftToTry == 0 {
324 return ""372 return ""
325 }373 }
@@ -331,7 +379,7 @@
331379
332// we reached the Started state, we can retry with the same host if we380// we reached the Started state, we can retry with the same host if we
333// have to retry again381// have to retry again
334func (sess *ClientSession) started() {382func (sess *clientSession) started() {
335 sess.tryHost--383 sess.tryHost--
336 if sess.tryHost == -1 {384 if sess.tryHost == -1 {
337 sess.tryHost = len(sess.deliveryHosts) - 1385 sess.tryHost = len(sess.deliveryHosts) - 1
@@ -341,7 +389,7 @@
341389
342// connect to a server using the configuration in the ClientSession390// connect to a server using the configuration in the ClientSession
343// and set up the connection.391// and set up the connection.
344func (sess *ClientSession) connect() error {392func (sess *clientSession) connect() error {
345 sess.setShouldDelay()393 sess.setShouldDelay()
346 sess.startConnectionAttempt()394 sess.startConnectionAttempt()
347 var err error395 var err error
@@ -363,49 +411,47 @@
363 return nil411 return nil
364}412}
365413
366func (sess *ClientSession) stopRedial() {414func (sess *clientSession) stopRedial() {
367 sess.retrierLock.Lock()
368 defer sess.retrierLock.Unlock()
369 if sess.retrier != nil {415 if sess.retrier != nil {
370 sess.retrier.Stop()416 sess.retrier.Stop()
371 sess.retrier = nil417 sess.retrier = nil
372 }418 }
373}419}
374420
375func (sess *ClientSession) AutoRedial(doneCh chan uint32) {421func (sess *clientSession) autoRedial() {
376 sess.stopRedial()422 sess.stopRedial()
377 if time.Since(sess.lastAutoRedial) < 2*time.Second {423 if time.Since(sess.lastAutoRedial) < 2*time.Second {
378 sess.setShouldDelay()424 sess.setShouldDelay()
379 }425 }
380 time.Sleep(sess.redialDelay(sess))426 // xxx should we really wait on the caller goroutine?
381 sess.retrierLock.Lock()427 delay := sess.redialDelay(sess)
382 defer sess.retrierLock.Unlock()428 sess.Log.Debugf("session redial delay: %v, wait", delay)
429 time.Sleep(delay)
430 sess.Log.Debugf("session redial delay: %v, cont", delay)
383 if sess.retrier != nil {431 if sess.retrier != nil {
384 panic("session AutoRedial: unexpected non-nil retrier.")432 panic("session AutoRedial: unexpected non-nil retrier.")
385 }433 }
386 sess.retrier = util.NewAutoRedialer(sess)434 sess.retrier = util.NewAutoRedialer(sess)
387 sess.lastAutoRedial = time.Now()435 sess.lastAutoRedial = time.Now()
388 go func() {436 go func(retrier util.AutoRedialer) {
389 sess.retrierLock.Lock()
390 retrier := sess.retrier
391 sess.retrierLock.Unlock()
392 if retrier == nil {
393 sess.Log.Debugf("session autoredialer skipping retry: retrier has been set to nil.")
394 return
395 }
396 sess.Log.Debugf("session autoredialier launching Redial goroutine")437 sess.Log.Debugf("session autoredialier launching Redial goroutine")
397 doneCh <- retrier.Redial()438 // if the redialer has been stopped before calling Redial(), it'll return 0.
398 }()439 sess.doneCh <- retrier.Redial()
399}440 }(sess.retrier)
400441}
401func (sess *ClientSession) Close() {442
402 sess.stopRedial()443func (sess *clientSession) doClose(resetCookie bool) {
403 sess.doClose()
404}
405
406func (sess *ClientSession) doClose() {
407 sess.connLock.Lock()444 sess.connLock.Lock()
408 defer sess.connLock.Unlock()445 defer sess.connLock.Unlock()
446 if resetCookie {
447 sess.cookie = ""
448 }
449 sess.closeConnection()
450 sess.setState(Disconnected)
451}
452
453func (sess *clientSession) closeConnection() {
454 // *must be called with connLock held*
409 if sess.Connection != nil {455 if sess.Connection != nil {
410 sess.Connection.Close()456 sess.Connection.Close()
411 // we ignore Close errors, on purpose (the thinking being that457 // we ignore Close errors, on purpose (the thinking being that
@@ -413,11 +459,10 @@
413 // you could do to recover at this stage).459 // you could do to recover at this stage).
414 sess.Connection = nil460 sess.Connection = nil
415 }461 }
416 sess.setState(Disconnected)
417}462}
418463
419// handle "ping" messages464// handle "ping" messages
420func (sess *ClientSession) handlePing() error {465func (sess *clientSession) handlePing() error {
421 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})466 err := sess.proto.WriteMessage(protocol.PingPongMsg{Type: "pong"})
422 if err == nil {467 if err == nil {
423 sess.Log.Debugf("ping.")468 sess.Log.Debugf("ping.")
@@ -429,7 +474,7 @@
429 return err474 return err
430}475}
431476
432func (sess *ClientSession) decodeBroadcast(bcast *serverMsg) *BroadcastNotification {477func (sess *clientSession) decodeBroadcast(bcast *serverMsg) *BroadcastNotification {
433 decoded := make([]map[string]interface{}, 0)478 decoded := make([]map[string]interface{}, 0)
434 for _, p := range bcast.Payloads {479 for _, p := range bcast.Payloads {
435 var v map[string]interface{}480 var v map[string]interface{}
@@ -447,7 +492,7 @@
447}492}
448493
449// handle "broadcast" messages494// handle "broadcast" messages
450func (sess *ClientSession) handleBroadcast(bcast *serverMsg) error {495func (sess *clientSession) handleBroadcast(bcast *serverMsg) error {
451 err := sess.SeenState.SetLevel(bcast.ChanId, bcast.TopLevel)496 err := sess.SeenState.SetLevel(bcast.ChanId, bcast.TopLevel)
452 if err != nil {497 if err != nil {
453 sess.setState(Error)498 sess.setState(Error)
@@ -478,7 +523,7 @@
478}523}
479524
480// handle "notifications" messages525// handle "notifications" messages
481func (sess *ClientSession) handleNotifications(ucast *serverMsg) error {526func (sess *clientSession) handleNotifications(ucast *serverMsg) error {
482 notifs, err := sess.SeenState.FilterBySeen(ucast.Notifications)527 notifs, err := sess.SeenState.FilterBySeen(ucast.Notifications)
483 if err != nil {528 if err != nil {
484 sess.setState(Error)529 sess.setState(Error)
@@ -512,7 +557,7 @@
512}557}
513558
514// handle "connbroken" messages559// handle "connbroken" messages
515func (sess *ClientSession) handleConnBroken(connBroken *serverMsg) error {560func (sess *clientSession) handleConnBroken(connBroken *serverMsg) error {
516 sess.setState(Error)561 sess.setState(Error)
517 reason := connBroken.Reason562 reason := connBroken.Reason
518 err := fmt.Errorf("server broke connection: %s", reason)563 err := fmt.Errorf("server broke connection: %s", reason)
@@ -525,7 +570,7 @@
525}570}
526571
527// handle "setparams" messages572// handle "setparams" messages
528func (sess *ClientSession) handleSetParams(setParams *serverMsg) error {573func (sess *clientSession) handleSetParams(setParams *serverMsg) error {
529 if setParams.SetCookie != "" {574 if setParams.SetCookie != "" {
530 sess.setCookie(setParams.SetCookie)575 sess.setCookie(setParams.SetCookie)
531 }576 }
@@ -533,7 +578,7 @@
533}578}
534579
535// loop runs the session with the server, emits a stream of events.580// loop runs the session with the server, emits a stream of events.
536func (sess *ClientSession) loop() error {581func (sess *clientSession) loop() error {
537 var err error582 var err error
538 var recv serverMsg583 var recv serverMsg
539 sess.setState(Running)584 sess.setState(Running)
@@ -571,7 +616,7 @@
571}616}
572617
573// Call this when you've connected and want to start looping.618// Call this when you've connected and want to start looping.
574func (sess *ClientSession) start() error {619func (sess *clientSession) start() error {
575 conn := sess.getConnection()620 conn := sess.getConnection()
576 err := conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout))621 err := conn.SetDeadline(time.Now().Add(sess.ExchangeTimeout))
577 if err != nil {622 if err != nil {
@@ -634,8 +679,8 @@
634679
635// run calls connect, and if it works it calls start, and if it works680// run calls connect, and if it works it calls start, and if it works
636// it runs loop in a goroutine, and ships its return value over ErrCh.681// it runs loop in a goroutine, and ships its return value over ErrCh.
637func (sess *ClientSession) run(closer func(), authChecker, hostGetter, connecter, starter, looper func() error) error {682func (sess *clientSession) run(closer func(bool), authChecker, hostGetter, connecter, starter, looper func() error) error {
638 closer()683 closer(false)
639 if err := authChecker(); err != nil {684 if err := authChecker(); err != nil {
640 return err685 return err
641 }686 }
@@ -646,17 +691,14 @@
646 if err == nil {691 if err == nil {
647 err = starter()692 err = starter()
648 if err == nil {693 if err == nil {
649 sess.ErrCh = make(chan error, 1)694 go func() { sess.errCh <- looper() }()
650 sess.BroadcastCh = make(chan *BroadcastNotification)
651 sess.NotificationsCh = make(chan AddressedNotification)
652 go func() { sess.ErrCh <- looper() }()
653 }695 }
654 }696 }
655 return err697 return err
656}698}
657699
658// This Jitter returns a random time.Duration somewhere in [-spread, spread].700// This Jitter returns a random time.Duration somewhere in [-spread, spread].
659func (sess *ClientSession) Jitter(spread time.Duration) time.Duration {701func (sess *clientSession) Jitter(spread time.Duration) time.Duration {
660 if spread < 0 {702 if spread < 0 {
661 panic("spread must be non-negative")703 panic("spread must be non-negative")
662 }704 }
@@ -666,7 +708,7 @@
666708
667// Dial takes the session from newly created (or newly disconnected)709// Dial takes the session from newly created (or newly disconnected)
668// to running the main loop.710// to running the main loop.
669func (sess *ClientSession) Dial() error {711func (sess *clientSession) Dial() error {
670 if sess.Protocolator == nil {712 if sess.Protocolator == nil {
671 // a missing protocolator means you've willfully overridden713 // a missing protocolator means you've willfully overridden
672 // it; returning an error here would prompt AutoRedial to just714 // it; returning an error here would prompt AutoRedial to just
@@ -676,6 +718,90 @@
676 return sess.run(sess.doClose, sess.addAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop)718 return sess.run(sess.doClose, sess.addAuthorization, sess.getHosts, sess.connect, sess.start, sess.loop)
677}719}
678720
721func (sess *clientSession) shutdown() {
722 sess.Log.Infof("session shutting down.")
723 sess.connLock.Lock()
724 defer sess.connLock.Unlock()
725 sess.stopRedial()
726 sess.closeConnection()
727}
728
729func (sess *clientSession) doKeepConnection() {
730 for {
731 select {
732 case cmd := <-sess.cmdCh:
733 switch cmd {
734 case cmdConnect:
735 sess.connHandler(true)
736 case cmdDisconnect:
737 sess.connHandler(false)
738 case cmdResetCookie:
739 sess.resetCookie()
740 }
741 case <-sess.stopCh:
742 sess.shutdown()
743 return
744 case n := <-sess.doneCh:
745 // if n == 0, the redialer aborted. If you do
746 // anything other than log it, keep that in mind.
747 sess.Log.Debugf("connected after %d attempts.", n)
748 case err := <-sess.errCh:
749 sess.errHandler(err)
750 }
751 }
752}
753
754func (sess *clientSession) handleConn(hasConn bool) {
755 sess.lastConn = hasConn
756
757 // Note this does not depend on the current state! That's because Dial
758 // starts with doClose, which gets you to Disconnected even if you're
759 // connected, and you can call Close when Disconnected without it
760 // losing its stuff.
761 if hasConn {
762 sess.autoRedial()
763 } else {
764 sess.stopRedial()
765 sess.doClose(false)
766 }
767}
768
769func (sess *clientSession) handleErr(err error) {
770 sess.Log.Errorf("session error'ed out with %v", err)
771 // State() == Error mostly defends interrupting an ongoing
772 // autoRedial if we went quickly already through hasConn =
773 // false => hasConn = true
774 if sess.State() == Error && sess.lastConn {
775 sess.autoRedial()
776 }
777}
778
779func (sess *clientSession) KeepConnection() error {
780 sess.stateLock.Lock()
781 defer sess.stateLock.Unlock()
782 if sess.state != Pristine {
783 return errors.New("don't call KeepConnection() on a non-pristine session.")
784 }
785 sess.state = Disconnected
786
787 go sess.doKeepConnection()
788
789 return nil
790}
791
792func (sess *clientSession) StopKeepConnection() {
793 sess.setState(Shutdown)
794 close(sess.stopCh)
795}
796
797func (sess *clientSession) HasConnectivity(hasConn bool) {
798 if hasConn {
799 sess.cmdCh <- cmdConnect
800 } else {
801 sess.cmdCh <- cmdDisconnect
802 }
803}
804
679func init() {805func init() {
680 rand.Seed(time.Now().Unix()) // good enough for us (we're not using it for crypto)806 rand.Seed(time.Now().Unix()) // good enough for us (we're not using it for crypto)
681}807}
682808
=== modified file 'client/session/session_test.go'
--- client/session/session_test.go 2014-10-23 19:30:24 +0000
+++ client/session/session_test.go 2015-03-26 16:42:21 +0000
@@ -162,6 +162,7 @@
162162
163func (*brokenSeenState) SetLevel(string, int64) error { return errors.New("broken.") }163func (*brokenSeenState) SetLevel(string, int64) error { return errors.New("broken.") }
164func (*brokenSeenState) GetAllLevels() (map[string]int64, error) { return nil, errors.New("broken.") }164func (*brokenSeenState) GetAllLevels() (map[string]int64, error) { return nil, errors.New("broken.") }
165func (*brokenSeenState) Close() {}
165func (*brokenSeenState) FilterBySeen([]protocol.Notification) ([]protocol.Notification, error) {166func (*brokenSeenState) FilterBySeen([]protocol.Notification) ([]protocol.Notification, error) {
166 return nil, errors.New("broken.")167 return nil, errors.New("broken.")
167}168}
@@ -189,6 +190,24 @@
189 cs.lvls = func() (seenstate.SeenState, error) { return seenstate.NewSqliteSeenState(":memory:") }190 cs.lvls = func() (seenstate.SeenState, error) { return seenstate.NewSqliteSeenState(":memory:") }
190}191}
191192
193func (cs *clientSessionSuite) TestStateString(c *C) {
194 for _, i := range []struct {
195 v ClientSessionState
196 s string
197 }{
198 {Error, "Error"},
199 {Pristine, "Pristine"},
200 {Disconnected, "Disconnected"},
201 {Connected, "Connected"},
202 {Started, "Started"},
203 {Running, "Running"},
204 {Shutdown, "Shutdown"},
205 {Unknown, fmt.Sprintf("??? (%d)", Unknown)},
206 } {
207 c.Check(i.v.String(), Equals, i.s)
208 }
209}
210
192/****************************************************************211/****************************************************************
193 parseServerAddrSpec() tests212 parseServerAddrSpec() tests
194****************************************************************/213****************************************************************/
@@ -211,10 +230,15 @@
211 NewSession() tests230 NewSession() tests
212****************************************************************/231****************************************************************/
213232
214var dummyConf = ClientSessionConfig{}233func dummyConf() ClientSessionConfig {
234 return ClientSessionConfig{
235 BroadcastCh: make(chan *BroadcastNotification, 5),
236 NotificationsCh: make(chan AddressedNotification, 5),
237 }
238}
215239
216func (cs *clientSessionSuite) TestNewSessionPlainWorks(c *C) {240func (cs *clientSessionSuite) TestNewSessionPlainWorks(c *C) {
217 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)241 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
218 c.Check(sess, NotNil)242 c.Check(sess, NotNil)
219 c.Check(err, IsNil)243 c.Check(err, IsNil)
220 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})244 c.Check(sess.fallbackHosts, DeepEquals, []string{"foo:443"})
@@ -224,11 +248,13 @@
224 c.Check(sess.redialDelays, DeepEquals, util.Timeouts())248 c.Check(sess.redialDelays, DeepEquals, util.Timeouts())
225 // but no root CAs set249 // but no root CAs set
226 c.Check(sess.TLS.RootCAs, IsNil)250 c.Check(sess.TLS.RootCAs, IsNil)
227 c.Check(sess.State(), Equals, Disconnected)251 c.Check(sess.State(), Equals, Pristine)
252 c.Check(sess.stopCh, NotNil)
253 c.Check(sess.cmdCh, NotNil)
228}254}
229255
230func (cs *clientSessionSuite) TestNewSessionHostEndpointWorks(c *C) {256func (cs *clientSessionSuite) TestNewSessionHostEndpointWorks(c *C) {
231 sess, err := NewSession("http://foo/hosts", dummyConf, "wah", cs.lvls, cs.log)257 sess, err := NewSession("http://foo/hosts", dummyConf(), "wah", cs.lvls, cs.log)
232 c.Assert(err, IsNil)258 c.Assert(err, IsNil)
233 c.Check(sess.getHost, NotNil)259 c.Check(sess.getHost, NotNil)
234}260}
@@ -254,7 +280,7 @@
254280
255func (cs *clientSessionSuite) TestNewSessionBadSeenStateFails(c *C) {281func (cs *clientSessionSuite) TestNewSessionBadSeenStateFails(c *C) {
256 ferr := func() (seenstate.SeenState, error) { return nil, errors.New("Busted.") }282 ferr := func() (seenstate.SeenState, error) { return nil, errors.New("Busted.") }
257 sess, err := NewSession("", dummyConf, "wah", ferr, cs.log)283 sess, err := NewSession("", dummyConf(), "wah", ferr, cs.log)
258 c.Check(sess, IsNil)284 c.Check(sess, IsNil)
259 c.Assert(err, NotNil)285 c.Assert(err, NotNil)
260}286}
@@ -265,7 +291,7 @@
265291
266func (cs *clientSessionSuite) TestGetHostsFallback(c *C) {292func (cs *clientSessionSuite) TestGetHostsFallback(c *C) {
267 fallback := []string{"foo:443", "bar:443"}293 fallback := []string{"foo:443", "bar:443"}
268 sess := &ClientSession{fallbackHosts: fallback}294 sess := &clientSession{fallbackHosts: fallback}
269 err := sess.getHosts()295 err := sess.getHosts()
270 c.Assert(err, IsNil)296 c.Assert(err, IsNil)
271 c.Check(sess.deliveryHosts, DeepEquals, fallback)297 c.Check(sess.deliveryHosts, DeepEquals, fallback)
@@ -283,14 +309,14 @@
283309
284func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {310func (cs *clientSessionSuite) TestGetHostsRemote(c *C) {
285 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}311 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
286 sess := &ClientSession{getHost: hostGetter, timeSince: time.Since}312 sess := &clientSession{getHost: hostGetter, timeSince: time.Since}
287 err := sess.getHosts()313 err := sess.getHosts()
288 c.Assert(err, IsNil)314 c.Assert(err, IsNil)
289 c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"})315 c.Check(sess.deliveryHosts, DeepEquals, []string{"foo:443", "bar:443"})
290}316}
291317
292func (cs *clientSessionSuite) TestGetHostsRemoteError(c *C) {318func (cs *clientSessionSuite) TestGetHostsRemoteError(c *C) {
293 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)319 sess, err := NewSession("", dummyConf(), "", cs.lvls, cs.log)
294 c.Assert(err, IsNil)320 c.Assert(err, IsNil)
295 hostsErr := errors.New("failed")321 hostsErr := errors.New("failed")
296 hostGetter := &testHostGetter{"", nil, hostsErr}322 hostGetter := &testHostGetter{"", nil, hostsErr}
@@ -303,7 +329,7 @@
303329
304func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {330func (cs *clientSessionSuite) TestGetHostsRemoteCaching(c *C) {
305 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}331 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
306 sess := &ClientSession{332 sess := &clientSession{
307 getHost: hostGetter,333 getHost: hostGetter,
308 ClientSessionConfig: ClientSessionConfig{334 ClientSessionConfig: ClientSessionConfig{
309 HostsCachingExpiryTime: 2 * time.Hour,335 HostsCachingExpiryTime: 2 * time.Hour,
@@ -328,7 +354,7 @@
328354
329func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {355func (cs *clientSessionSuite) TestGetHostsRemoteCachingReset(c *C) {
330 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}356 hostGetter := &testHostGetter{"example.com", []string{"foo:443", "bar:443"}, nil}
331 sess := &ClientSession{357 sess := &clientSession{
332 getHost: hostGetter,358 getHost: hostGetter,
333 ClientSessionConfig: ClientSessionConfig{359 ClientSessionConfig: ClientSessionConfig{
334 HostsCachingExpiryTime: 2 * time.Hour,360 HostsCachingExpiryTime: 2 * time.Hour,
@@ -355,7 +381,7 @@
355381
356func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {382func (cs *clientSessionSuite) TestAddAuthorizationAddsAuthorization(c *C) {
357 url := "xyzzy://"383 url := "xyzzy://"
358 sess := &ClientSession{Log: cs.log}384 sess := &clientSession{Log: cs.log}
359 sess.AuthGetter = func(url string) string { return url + " auth'ed" }385 sess.AuthGetter = func(url string) string { return url + " auth'ed" }
360 sess.AuthURL = url386 sess.AuthURL = url
361 c.Assert(sess.auth, Equals, "")387 c.Assert(sess.auth, Equals, "")
@@ -365,7 +391,7 @@
365}391}
366392
367func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {393func (cs *clientSessionSuite) TestAddAuthorizationSkipsIfUnset(c *C) {
368 sess := &ClientSession{Log: cs.log}394 sess := &clientSession{Log: cs.log}
369 sess.AuthGetter = nil395 sess.AuthGetter = nil
370 c.Assert(sess.auth, Equals, "")396 c.Assert(sess.auth, Equals, "")
371 err := sess.addAuthorization()397 err := sess.addAuthorization()
@@ -379,7 +405,7 @@
379405
380func (cs *clientSessionSuite) TestStartConnectionAttempt(c *C) {406func (cs *clientSessionSuite) TestStartConnectionAttempt(c *C) {
381 since := time.Since(time.Time{})407 since := time.Since(time.Time{})
382 sess := &ClientSession{408 sess := &clientSession{
383 ClientSessionConfig: ClientSessionConfig{409 ClientSessionConfig: ClientSessionConfig{
384 ExpectAllRepairedTime: 10 * time.Second,410 ExpectAllRepairedTime: 10 * time.Second,
385 },411 },
@@ -403,7 +429,7 @@
403429
404func (cs *clientSessionSuite) TestStartConnectionAttemptNoHostsPanic(c *C) {430func (cs *clientSessionSuite) TestStartConnectionAttemptNoHostsPanic(c *C) {
405 since := time.Since(time.Time{})431 since := time.Since(time.Time{})
406 sess := &ClientSession{432 sess := &clientSession{
407 ClientSessionConfig: ClientSessionConfig{433 ClientSessionConfig: ClientSessionConfig{
408 ExpectAllRepairedTime: 10 * time.Second,434 ExpectAllRepairedTime: 10 * time.Second,
409 },435 },
@@ -415,7 +441,7 @@
415}441}
416442
417func (cs *clientSessionSuite) TestNextHostToTry(c *C) {443func (cs *clientSessionSuite) TestNextHostToTry(c *C) {
418 sess := &ClientSession{444 sess := &clientSession{
419 deliveryHosts: []string{"foo:443", "bar:443", "baz:443"},445 deliveryHosts: []string{"foo:443", "bar:443", "baz:443"},
420 tryHost: 0,446 tryHost: 0,
421 leftToTry: 3,447 leftToTry: 3,
@@ -438,7 +464,7 @@
438}464}
439465
440func (cs *clientSessionSuite) TestStarted(c *C) {466func (cs *clientSessionSuite) TestStarted(c *C) {
441 sess, err := NewSession("", dummyConf, "", cs.lvls, cs.log)467 sess, err := NewSession("", dummyConf(), "", cs.lvls, cs.log)
442 c.Assert(err, IsNil)468 c.Assert(err, IsNil)
443469
444 sess.deliveryHosts = []string{"foo:443", "bar:443", "baz:443"}470 sess.deliveryHosts = []string{"foo:443", "bar:443", "baz:443"}
@@ -457,7 +483,7 @@
457****************************************************************/483****************************************************************/
458484
459func (cs *clientSessionSuite) TestConnectFailsWithNoAddress(c *C) {485func (cs *clientSessionSuite) TestConnectFailsWithNoAddress(c *C) {
460 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)486 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
461 c.Assert(err, IsNil)487 c.Assert(err, IsNil)
462 sess.deliveryHosts = []string{"nowhere"}488 sess.deliveryHosts = []string{"nowhere"}
463 sess.clearShouldDelay()489 sess.clearShouldDelay()
@@ -471,7 +497,7 @@
471 srv, err := net.Listen("tcp", "localhost:0")497 srv, err := net.Listen("tcp", "localhost:0")
472 c.Assert(err, IsNil)498 c.Assert(err, IsNil)
473 defer srv.Close()499 defer srv.Close()
474 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)500 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
475 c.Assert(err, IsNil)501 c.Assert(err, IsNil)
476 sess.deliveryHosts = []string{srv.Addr().String()}502 sess.deliveryHosts = []string{srv.Addr().String()}
477 sess.clearShouldDelay()503 sess.clearShouldDelay()
@@ -486,7 +512,7 @@
486 srv, err := net.Listen("tcp", "localhost:0")512 srv, err := net.Listen("tcp", "localhost:0")
487 c.Assert(err, IsNil)513 c.Assert(err, IsNil)
488 defer srv.Close()514 defer srv.Close()
489 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)515 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
490 c.Assert(err, IsNil)516 c.Assert(err, IsNil)
491 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}517 sess.deliveryHosts = []string{"nowhere", srv.Addr().String()}
492 sess.clearShouldDelay()518 sess.clearShouldDelay()
@@ -501,7 +527,7 @@
501func (cs *clientSessionSuite) TestConnectConnectFail(c *C) {527func (cs *clientSessionSuite) TestConnectConnectFail(c *C) {
502 srv, err := net.Listen("tcp", "localhost:0")528 srv, err := net.Listen("tcp", "localhost:0")
503 c.Assert(err, IsNil)529 c.Assert(err, IsNil)
504 sess, err := NewSession(srv.Addr().String(), dummyConf, "wah", cs.lvls, cs.log)530 sess, err := NewSession(srv.Addr().String(), dummyConf(), "wah", cs.lvls, cs.log)
505 srv.Close()531 srv.Close()
506 c.Assert(err, IsNil)532 c.Assert(err, IsNil)
507 sess.deliveryHosts = []string{srv.Addr().String()}533 sess.deliveryHosts = []string{srv.Addr().String()}
@@ -512,101 +538,58 @@
512 c.Check(sess.State(), Equals, Error)538 c.Check(sess.State(), Equals, Error)
513}539}
514540
515/****************************************************************541type dumbRetrier struct{ stopped bool }
516 Close() tests542
517****************************************************************/543func (*dumbRetrier) Redial() uint32 { return 0 }
518544func (d *dumbRetrier) Stop() { d.stopped = true }
519func (cs *clientSessionSuite) TestClose(c *C) {545
520 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)546// /****************************************************************
521 c.Assert(err, IsNil)547// AutoRedial() tests
522 sess.Connection = &testConn{Name: "TestClose"}548// ****************************************************************/
523 sess.Close()
524 c.Check(sess.Connection, IsNil)
525 c.Check(sess.State(), Equals, Disconnected)
526}
527
528func (cs *clientSessionSuite) TestCloseTwice(c *C) {
529 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
530 c.Assert(err, IsNil)
531 sess.Connection = &testConn{Name: "TestCloseTwice"}
532 sess.Close()
533 c.Check(sess.Connection, IsNil)
534 sess.Close()
535 c.Check(sess.Connection, IsNil)
536 c.Check(sess.State(), Equals, Disconnected)
537}
538
539func (cs *clientSessionSuite) TestCloseFails(c *C) {
540 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
541 c.Assert(err, IsNil)
542 sess.Connection = &testConn{Name: "TestCloseFails", CloseCondition: condition.Work(false)}
543 sess.Close()
544 c.Check(sess.Connection, IsNil) // nothing you can do to clean up anyway
545 c.Check(sess.State(), Equals, Disconnected)
546}
547
548type derp struct{ stopped bool }
549
550func (*derp) Redial() uint32 { return 0 }
551func (d *derp) Stop() { d.stopped = true }
552
553func (cs *clientSessionSuite) TestCloseStopsRetrier(c *C) {
554 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)
555 c.Assert(err, IsNil)
556 ar := new(derp)
557 sess.retrier = ar
558 c.Check(ar.stopped, Equals, false)
559 sess.Close()
560 c.Check(ar.stopped, Equals, true)
561 sess.Close() // double close check
562 c.Check(ar.stopped, Equals, true)
563}
564
565/****************************************************************
566 AutoRedial() tests
567****************************************************************/
568549
569func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) {550func (cs *clientSessionSuite) TestAutoRedialWorks(c *C) {
570 // checks that AutoRedial sets up a retrier and tries redialing it551 // checks that AutoRedial sets up a retrier and tries redialing it
571 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)552 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
572 c.Assert(err, IsNil)553 c.Assert(err, IsNil)
573 ar := new(derp)554 ar := new(dumbRetrier)
574 sess.retrier = ar555 sess.retrier = ar
575 c.Check(ar.stopped, Equals, false)556 c.Check(ar.stopped, Equals, false)
576 sess.AutoRedial(nil)557 sess.autoRedial()
558 defer sess.stopRedial()
577 c.Check(ar.stopped, Equals, true)559 c.Check(ar.stopped, Equals, true)
578}560}
579561
580func (cs *clientSessionSuite) TestAutoRedialStopsRetrier(c *C) {562func (cs *clientSessionSuite) TestAutoRedialStopsRetrier(c *C) {
581 // checks that AutoRedial stops the previous retrier563 // checks that AutoRedial stops the previous retrier
582 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)564 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
583 c.Assert(err, IsNil)565 c.Assert(err, IsNil)
584 ch := make(chan uint32)566 sess.doneCh = make(chan uint32)
585 c.Check(sess.retrier, IsNil)567 c.Check(sess.retrier, IsNil)
586 sess.AutoRedial(ch)568 sess.autoRedial()
587 c.Assert(sess.retrier, NotNil)569 c.Assert(sess.retrier, NotNil)
588 sess.retrier.Stop()570 sess.retrier.Stop()
589 c.Check(<-ch, Not(Equals), 0)571 c.Check(<-sess.doneCh, Not(Equals), 0)
590}572}
591573
592func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {574func (cs *clientSessionSuite) TestAutoRedialCallsRedialDelay(c *C) {
593 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)575 // NOTE there are tests that use calling redialDelay as an indication of calling autoRedial!
576 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
594 c.Assert(err, IsNil)577 c.Assert(err, IsNil)
595 flag := false578 flag := false
596 sess.redialDelay = func(sess *ClientSession) time.Duration { flag = true; return 0 }579 sess.redialDelay = func(sess *clientSession) time.Duration { flag = true; return 0 }
597 sess.AutoRedial(nil)580 sess.autoRedial()
598 c.Check(flag, Equals, true)581 c.Check(flag, Equals, true)
599}582}
600583
601func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {584func (cs *clientSessionSuite) TestAutoRedialSetsRedialDelayIfTooQuick(c *C) {
602 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)585 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
603 c.Assert(err, IsNil)586 c.Assert(err, IsNil)
604 sess.redialDelay = func(sess *ClientSession) time.Duration { return 0 }587 sess.redialDelay = func(sess *clientSession) time.Duration { return 0 }
605 sess.AutoRedial(nil)588 sess.autoRedial()
606 c.Check(sess.ShouldDelay(), Equals, false)589 c.Check(sess.ShouldDelay(), Equals, false)
607 sess.stopRedial()590 sess.stopRedial()
608 sess.clearShouldDelay()591 sess.clearShouldDelay()
609 sess.AutoRedial(nil)592 sess.autoRedial()
610 c.Check(sess.ShouldDelay(), Equals, true)593 c.Check(sess.ShouldDelay(), Equals, true)
611}594}
612595
@@ -615,29 +598,23 @@
615****************************************************************/598****************************************************************/
616599
617type msgSuite struct {600type msgSuite struct {
618 sess *ClientSession601 sess *clientSession
619 upCh chan interface{}602 upCh chan interface{}
620 downCh chan interface{}603 downCh chan interface{}
621 errCh chan error
622}604}
623605
624var _ = Suite(&msgSuite{})606var _ = Suite(&msgSuite{})
625607
626func (s *msgSuite) SetUpTest(c *C) {608func (s *msgSuite) SetUpTest(c *C) {
627 var err error609 var err error
628 conf := ClientSessionConfig{610 conf := dummyConf()
629 ExchangeTimeout: time.Millisecond,611 conf.ExchangeTimeout = time.Millisecond
630 }
631 s.sess, err = NewSession("", conf, "wah", seenstate.NewSeenState, helpers.NewTestLogger(c, "debug"))612 s.sess, err = NewSession("", conf, "wah", seenstate.NewSeenState, helpers.NewTestLogger(c, "debug"))
632 c.Assert(err, IsNil)613 c.Assert(err, IsNil)
633 s.sess.Connection = &testConn{Name: "TestHandle*"}614 s.sess.Connection = &testConn{Name: "TestHandle*"}
634 s.errCh = make(chan error, 1)
635 s.upCh = make(chan interface{}, 5)615 s.upCh = make(chan interface{}, 5)
636 s.downCh = make(chan interface{}, 5)616 s.downCh = make(chan interface{}, 5)
637 s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh}617 s.sess.proto = &testProtocol{up: s.upCh, down: s.downCh}
638 // make the message channel buffered
639 s.sess.BroadcastCh = make(chan *BroadcastNotification, 5)
640 s.sess.NotificationsCh = make(chan AddressedNotification, 5)
641}618}
642619
643func (s *msgSuite) TestHandlePingWorks(c *C) {620func (s *msgSuite) TestHandlePingWorks(c *C) {
@@ -693,10 +670,10 @@
693 json.RawMessage(`{"img1/m1":[102,"tubular"]}`),670 json.RawMessage(`{"img1/m1":[102,"tubular"]}`),
694 },671 },
695 }672 }
696 go func() { s.errCh <- s.sess.handleBroadcast(msg) }()673 go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
697 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})674 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
698 s.upCh <- nil // ack ok675 s.upCh <- nil // ack ok
699 c.Check(<-s.errCh, Equals, nil)676 c.Check(<-s.sess.errCh, Equals, nil)
700 c.Assert(len(s.sess.BroadcastCh), Equals, 1)677 c.Assert(len(s.sess.BroadcastCh), Equals, 1)
701 c.Check(<-s.sess.BroadcastCh, DeepEquals, &BroadcastNotification{678 c.Check(<-s.sess.BroadcastCh, DeepEquals, &BroadcastNotification{
702 TopLevel: 2,679 TopLevel: 2,
@@ -725,11 +702,11 @@
725 TopLevel: 2,702 TopLevel: 2,
726 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},703 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
727 }704 }
728 go func() { s.errCh <- s.sess.handleBroadcast(msg) }()705 go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
729 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})706 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
730 failure := errors.New("ACK ACK ACK")707 failure := errors.New("ACK ACK ACK")
731 s.upCh <- failure708 s.upCh <- failure
732 c.Assert(<-s.errCh, Equals, failure)709 c.Assert(<-s.sess.errCh, Equals, failure)
733 c.Check(s.sess.State(), Equals, Error)710 c.Check(s.sess.State(), Equals, Error)
734}711}
735712
@@ -743,10 +720,10 @@
743 TopLevel: 2,720 TopLevel: 2,
744 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},721 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
745 }722 }
746 go func() { s.errCh <- s.sess.handleBroadcast(msg) }()723 go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
747 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})724 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
748 s.upCh <- nil // ack ok725 s.upCh <- nil // ack ok
749 c.Check(<-s.errCh, IsNil)726 c.Check(<-s.sess.errCh, IsNil)
750 c.Check(len(s.sess.BroadcastCh), Equals, 0)727 c.Check(len(s.sess.BroadcastCh), Equals, 0)
751}728}
752729
@@ -761,10 +738,10 @@
761 TopLevel: 2,738 TopLevel: 2,
762 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},739 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
763 }740 }
764 go func() { s.errCh <- s.sess.handleBroadcast(msg) }()741 go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
765 s.upCh <- nil // ack ok742 s.upCh <- nil // ack ok
766 // start returns with error743 // start returns with error
767 c.Check(<-s.errCh, Not(Equals), nil)744 c.Check(<-s.sess.errCh, Not(Equals), nil)
768 c.Check(s.sess.State(), Equals, Error)745 c.Check(s.sess.State(), Equals, Error)
769 // no message sent out746 // no message sent out
770 c.Check(len(s.sess.BroadcastCh), Equals, 0)747 c.Check(len(s.sess.BroadcastCh), Equals, 0)
@@ -777,10 +754,10 @@
777 s.sess.setShouldDelay()754 s.sess.setShouldDelay()
778755
779 msg := &serverMsg{Type: "broadcast"}756 msg := &serverMsg{Type: "broadcast"}
780 go func() { s.errCh <- s.sess.handleBroadcast(msg) }()757 go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
781 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})758 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
782 s.upCh <- nil // ack ok759 s.upCh <- nil // ack ok
783 c.Check(<-s.errCh, IsNil)760 c.Check(<-s.sess.errCh, IsNil)
784761
785 c.Check(s.sess.ShouldDelay(), Equals, false)762 c.Check(s.sess.ShouldDelay(), Equals, false)
786}763}
@@ -789,10 +766,10 @@
789 s.sess.setShouldDelay()766 s.sess.setShouldDelay()
790767
791 msg := &serverMsg{Type: "broadcast"}768 msg := &serverMsg{Type: "broadcast"}
792 go func() { s.errCh <- s.sess.handleBroadcast(msg) }()769 go func() { s.sess.errCh <- s.sess.handleBroadcast(msg) }()
793 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})770 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
794 s.upCh <- errors.New("bcast")771 s.upCh <- errors.New("bcast")
795 c.Check(<-s.errCh, NotNil)772 c.Check(<-s.sess.errCh, NotNil)
796773
797 c.Check(s.sess.ShouldDelay(), Equals, true)774 c.Check(s.sess.ShouldDelay(), Equals, true)
798}775}
@@ -842,10 +819,10 @@
842 msg.NotificationsMsg = protocol.NotificationsMsg{819 msg.NotificationsMsg = protocol.NotificationsMsg{
843 Notifications: []protocol.Notification{n1, n2},820 Notifications: []protocol.Notification{n1, n2},
844 }821 }
845 go func() { s.errCh <- s.sess.handleNotifications(msg) }()822 go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
846 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})823 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
847 s.upCh <- nil // ack ok824 s.upCh <- nil // ack ok
848 c.Check(<-s.errCh, Equals, nil)825 c.Check(<-s.sess.errCh, Equals, nil)
849 c.Check(s.sess.ShouldDelay(), Equals, false)826 c.Check(s.sess.ShouldDelay(), Equals, false)
850 c.Assert(s.sess.NotificationsCh, HasLen, 2)827 c.Assert(s.sess.NotificationsCh, HasLen, 2)
851 app1, err := click.ParseAppId("com.example.app1_app1")828 app1, err := click.ParseAppId("com.example.app1_app1")
@@ -888,10 +865,10 @@
888 msg.NotificationsMsg = protocol.NotificationsMsg{865 msg.NotificationsMsg = protocol.NotificationsMsg{
889 Notifications: []protocol.Notification{n1, n2},866 Notifications: []protocol.Notification{n1, n2},
890 }867 }
891 go func() { s.errCh <- s.sess.handleNotifications(msg) }()868 go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
892 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})869 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
893 s.upCh <- nil // ack ok870 s.upCh <- nil // ack ok
894 c.Check(<-s.errCh, Equals, nil)871 c.Check(<-s.sess.errCh, Equals, nil)
895 c.Check(s.sess.ShouldDelay(), Equals, false)872 c.Check(s.sess.ShouldDelay(), Equals, false)
896 c.Assert(s.sess.NotificationsCh, HasLen, 1)873 c.Assert(s.sess.NotificationsCh, HasLen, 1)
897 app2, err := click.ParseAppId("com.example.app2_app2")874 app2, err := click.ParseAppId("com.example.app2_app2")
@@ -923,10 +900,10 @@
923 msg.NotificationsMsg = protocol.NotificationsMsg{900 msg.NotificationsMsg = protocol.NotificationsMsg{
924 Notifications: []protocol.Notification{n1, n2},901 Notifications: []protocol.Notification{n1, n2},
925 }902 }
926 go func() { s.errCh <- s.sess.handleNotifications(msg) }()903 go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
927 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})904 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
928 s.upCh <- nil // ack ok905 s.upCh <- nil // ack ok
929 c.Check(<-s.errCh, Equals, nil)906 c.Check(<-s.sess.errCh, Equals, nil)
930 c.Assert(s.sess.NotificationsCh, HasLen, 2)907 c.Assert(s.sess.NotificationsCh, HasLen, 2)
931 app1, err := click.ParseAppId("com.example.app1_app1")908 app1, err := click.ParseAppId("com.example.app1_app1")
932 c.Assert(err, IsNil)909 c.Assert(err, IsNil)
@@ -943,10 +920,10 @@
943 c.Check(ac.ops, HasLen, 3)920 c.Check(ac.ops, HasLen, 3)
944921
945 // second time they get ignored922 // second time they get ignored
946 go func() { s.errCh <- s.sess.handleNotifications(msg) }()923 go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
947 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})924 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
948 s.upCh <- nil // ack ok925 s.upCh <- nil // ack ok
949 c.Check(<-s.errCh, Equals, nil)926 c.Check(<-s.sess.errCh, Equals, nil)
950 c.Assert(s.sess.NotificationsCh, HasLen, 0)927 c.Assert(s.sess.NotificationsCh, HasLen, 0)
951 c.Check(ac.ops, HasLen, 4)928 c.Check(ac.ops, HasLen, 4)
952}929}
@@ -963,11 +940,11 @@
963 msg.NotificationsMsg = protocol.NotificationsMsg{940 msg.NotificationsMsg = protocol.NotificationsMsg{
964 Notifications: []protocol.Notification{n1},941 Notifications: []protocol.Notification{n1},
965 }942 }
966 go func() { s.errCh <- s.sess.handleNotifications(msg) }()943 go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
967 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})944 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
968 failure := errors.New("ACK ACK ACK")945 failure := errors.New("ACK ACK ACK")
969 s.upCh <- failure946 s.upCh <- failure
970 c.Assert(<-s.errCh, Equals, failure)947 c.Assert(<-s.sess.errCh, Equals, failure)
971 c.Check(s.sess.State(), Equals, Error)948 c.Check(s.sess.State(), Equals, Error)
972 // didn't get to clear949 // didn't get to clear
973 c.Check(s.sess.ShouldDelay(), Equals, true)950 c.Check(s.sess.ShouldDelay(), Equals, true)
@@ -986,10 +963,10 @@
986 msg.NotificationsMsg = protocol.NotificationsMsg{963 msg.NotificationsMsg = protocol.NotificationsMsg{
987 Notifications: []protocol.Notification{n1},964 Notifications: []protocol.Notification{n1},
988 }965 }
989 go func() { s.errCh <- s.sess.handleNotifications(msg) }()966 go func() { s.sess.errCh <- s.sess.handleNotifications(msg) }()
990 s.upCh <- nil // ack ok967 s.upCh <- nil // ack ok
991 // start returns with error968 // start returns with error
992 c.Check(<-s.errCh, Not(Equals), nil)969 c.Check(<-s.sess.errCh, Not(Equals), nil)
993 c.Check(s.sess.State(), Equals, Error)970 c.Check(s.sess.State(), Equals, Error)
994 // no message sent out971 // no message sent out
995 c.Check(len(s.sess.NotificationsCh), Equals, 0)972 c.Check(len(s.sess.NotificationsCh), Equals, 0)
@@ -1010,8 +987,8 @@
1010 msg.ConnBrokenMsg = protocol.ConnBrokenMsg{987 msg.ConnBrokenMsg = protocol.ConnBrokenMsg{
1011 Reason: "REASON",988 Reason: "REASON",
1012 }989 }
1013 go func() { s.errCh <- s.sess.handleConnBroken(msg) }()990 go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }()
1014 c.Check(<-s.errCh, ErrorMatches, "server broke connection: REASON")991 c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: REASON")
1015 c.Check(s.sess.State(), Equals, Error)992 c.Check(s.sess.State(), Equals, Error)
1016}993}
1017994
@@ -1022,8 +999,8 @@
1022 Reason: protocol.BrokenHostMismatch,999 Reason: protocol.BrokenHostMismatch,
1023 }1000 }
1024 s.sess.deliveryHosts = []string{"foo:443", "bar:443"}1001 s.sess.deliveryHosts = []string{"foo:443", "bar:443"}
1025 go func() { s.errCh <- s.sess.handleConnBroken(msg) }()1002 go func() { s.sess.errCh <- s.sess.handleConnBroken(msg) }()
1026 c.Check(<-s.errCh, ErrorMatches, "server broke connection: host-mismatch")1003 c.Check(<-s.sess.errCh, ErrorMatches, "server broke connection: host-mismatch")
1027 c.Check(s.sess.State(), Equals, Error)1004 c.Check(s.sess.State(), Equals, Error)
1028 // hosts were reset1005 // hosts were reset
1029 c.Check(s.sess.deliveryHosts, IsNil)1006 c.Check(s.sess.deliveryHosts, IsNil)
@@ -1041,14 +1018,14 @@
1041 (*msgSuite)(s).SetUpTest(c)1018 (*msgSuite)(s).SetUpTest(c)
1042 s.sess.Connection.(*testConn).Name = "TestLoop*"1019 s.sess.Connection.(*testConn).Name = "TestLoop*"
1043 go func() {1020 go func() {
1044 s.errCh <- s.sess.loop()1021 s.sess.errCh <- s.sess.loop()
1045 }()1022 }()
1046}1023}
10471024
1048func (s *loopSuite) TestLoopReadError(c *C) {1025func (s *loopSuite) TestLoopReadError(c *C) {
1049 c.Check(s.sess.State(), Equals, Running)1026 c.Check(s.sess.State(), Equals, Running)
1050 s.upCh <- errors.New("Read")1027 s.upCh <- errors.New("Read")
1051 err := <-s.errCh1028 err := <-s.sess.errCh
1052 c.Check(err, ErrorMatches, "Read")1029 c.Check(err, ErrorMatches, "Read")
1053 c.Check(s.sess.State(), Equals, Error)1030 c.Check(s.sess.State(), Equals, Error)
1054}1031}
@@ -1060,7 +1037,7 @@
1060 c.Check(takeNext(s.downCh), Equals, protocol.PingPongMsg{Type: "pong"})1037 c.Check(takeNext(s.downCh), Equals, protocol.PingPongMsg{Type: "pong"})
1061 failure := errors.New("pong")1038 failure := errors.New("pong")
1062 s.upCh <- failure1039 s.upCh <- failure
1063 c.Check(<-s.errCh, Equals, failure)1040 c.Check(<-s.sess.errCh, Equals, failure)
1064}1041}
10651042
1066func (s *loopSuite) TestLoopLoopsDaLoop(c *C) {1043func (s *loopSuite) TestLoopLoopsDaLoop(c *C) {
@@ -1073,7 +1050,7 @@
1073 }1050 }
1074 failure := errors.New("pong")1051 failure := errors.New("pong")
1075 s.upCh <- failure1052 s.upCh <- failure
1076 c.Check(<-s.errCh, Equals, failure)1053 c.Check(<-s.sess.errCh, Equals, failure)
1077}1054}
10781055
1079func (s *loopSuite) TestLoopBroadcast(c *C) {1056func (s *loopSuite) TestLoopBroadcast(c *C) {
@@ -1090,7 +1067,7 @@
1090 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})1067 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
1091 failure := errors.New("ack")1068 failure := errors.New("ack")
1092 s.upCh <- failure1069 s.upCh <- failure
1093 c.Check(<-s.errCh, Equals, failure)1070 c.Check(<-s.sess.errCh, Equals, failure)
1094}1071}
10951072
1096func (s *loopSuite) TestLoopNotifications(c *C) {1073func (s *loopSuite) TestLoopNotifications(c *C) {
@@ -1110,7 +1087,7 @@
1110 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})1087 c.Check(takeNext(s.downCh), Equals, protocol.AckMsg{"ack"})
1111 failure := errors.New("ack")1088 failure := errors.New("ack")
1112 s.upCh <- failure1089 s.upCh <- failure
1113 c.Check(<-s.errCh, Equals, failure)1090 c.Check(<-s.sess.errCh, Equals, failure)
1114}1091}
11151092
1116func (s *loopSuite) TestLoopSetParams(c *C) {1093func (s *loopSuite) TestLoopSetParams(c *C) {
@@ -1123,7 +1100,7 @@
1123 s.upCh <- setParams1100 s.upCh <- setParams
1124 failure := errors.New("fail")1101 failure := errors.New("fail")
1125 s.upCh <- failure1102 s.upCh <- failure
1126 c.Assert(<-s.errCh, Equals, failure)1103 c.Assert(<-s.sess.errCh, Equals, failure)
1127 c.Check(s.sess.getCookie(), Equals, "COOKIE")1104 c.Check(s.sess.getCookie(), Equals, "COOKIE")
1128}1105}
11291106
@@ -1135,7 +1112,7 @@
1135 }1112 }
1136 c.Check(takeNext(s.downCh), Equals, "deadline 1ms")1113 c.Check(takeNext(s.downCh), Equals, "deadline 1ms")
1137 s.upCh <- broken1114 s.upCh <- broken
1138 c.Check(<-s.errCh, NotNil)1115 c.Check(<-s.sess.errCh, NotNil)
1139}1116}
11401117
1141func (s *loopSuite) TestLoopConnWarn(c *C) {1118func (s *loopSuite) TestLoopConnWarn(c *C) {
@@ -1156,7 +1133,7 @@
1156 s.upCh <- warn1133 s.upCh <- warn
1157 s.upCh <- connwarn1134 s.upCh <- connwarn
1158 s.upCh <- failure1135 s.upCh <- failure
1159 c.Check(<-s.errCh, Equals, failure)1136 c.Check(<-s.sess.errCh, Equals, failure)
1160 c.Check(log.Captured(),1137 c.Check(log.Captured(),
1161 Matches, `(?ms).* warning: XXX$.*`)1138 Matches, `(?ms).* warning: XXX$.*`)
1162 c.Check(log.Captured(),1139 c.Check(log.Captured(),
@@ -1167,7 +1144,7 @@
1167 start() tests1144 start() tests
1168****************************************************************/1145****************************************************************/
1169func (cs *clientSessionSuite) TestStartFailsIfSetDeadlineFails(c *C) {1146func (cs *clientSessionSuite) TestStartFailsIfSetDeadlineFails(c *C) {
1170 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1147 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1171 c.Assert(err, IsNil)1148 c.Assert(err, IsNil)
1172 sess.Connection = &testConn{Name: "TestStartFailsIfSetDeadlineFails",1149 sess.Connection = &testConn{Name: "TestStartFailsIfSetDeadlineFails",
1173 DeadlineCondition: condition.Work(false)} // setdeadline will fail1150 DeadlineCondition: condition.Work(false)} // setdeadline will fail
@@ -1177,7 +1154,7 @@
1177}1154}
11781155
1179func (cs *clientSessionSuite) TestStartFailsIfWriteFails(c *C) {1156func (cs *clientSessionSuite) TestStartFailsIfWriteFails(c *C) {
1180 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1157 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1181 c.Assert(err, IsNil)1158 c.Assert(err, IsNil)
1182 sess.Connection = &testConn{Name: "TestStartFailsIfWriteFails",1159 sess.Connection = &testConn{Name: "TestStartFailsIfWriteFails",
1183 WriteCondition: condition.Work(false)} // write will fail1160 WriteCondition: condition.Work(false)} // write will fail
@@ -1187,7 +1164,7 @@
1187}1164}
11881165
1189func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) {1166func (cs *clientSessionSuite) TestStartFailsIfGetLevelsFails(c *C) {
1190 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1167 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1191 c.Assert(err, IsNil)1168 c.Assert(err, IsNil)
1192 sess.SeenState = &brokenSeenState{}1169 sess.SeenState = &brokenSeenState{}
1193 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}1170 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}
@@ -1207,7 +1184,7 @@
1207}1184}
12081185
1209func (cs *clientSessionSuite) TestStartConnectMessageFails(c *C) {1186func (cs *clientSessionSuite) TestStartConnectMessageFails(c *C) {
1210 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1187 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1211 c.Assert(err, IsNil)1188 c.Assert(err, IsNil)
1212 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}1189 sess.Connection = &testConn{Name: "TestStartConnectMessageFails"}
1213 errCh := make(chan error, 1)1190 errCh := make(chan error, 1)
@@ -1234,7 +1211,7 @@
1234}1211}
12351212
1236func (cs *clientSessionSuite) TestStartConnackReadError(c *C) {1213func (cs *clientSessionSuite) TestStartConnackReadError(c *C) {
1237 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1214 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1238 c.Assert(err, IsNil)1215 c.Assert(err, IsNil)
1239 sess.Connection = &testConn{Name: "TestStartConnackReadError"}1216 sess.Connection = &testConn{Name: "TestStartConnackReadError"}
1240 errCh := make(chan error, 1)1217 errCh := make(chan error, 1)
@@ -1258,7 +1235,7 @@
1258}1235}
12591236
1260func (cs *clientSessionSuite) TestStartBadConnack(c *C) {1237func (cs *clientSessionSuite) TestStartBadConnack(c *C) {
1261 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1238 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1262 c.Assert(err, IsNil)1239 c.Assert(err, IsNil)
1263 sess.Connection = &testConn{Name: "TestStartBadConnack"}1240 sess.Connection = &testConn{Name: "TestStartBadConnack"}
1264 errCh := make(chan error, 1)1241 errCh := make(chan error, 1)
@@ -1282,7 +1259,7 @@
1282}1259}
12831260
1284func (cs *clientSessionSuite) TestStartNotConnack(c *C) {1261func (cs *clientSessionSuite) TestStartNotConnack(c *C) {
1285 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1262 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1286 c.Assert(err, IsNil)1263 c.Assert(err, IsNil)
1287 sess.Connection = &testConn{Name: "TestStartBadConnack"}1264 sess.Connection = &testConn{Name: "TestStartBadConnack"}
1288 errCh := make(chan error, 1)1265 errCh := make(chan error, 1)
@@ -1349,13 +1326,31 @@
1349 run() tests1326 run() tests
1350****************************************************************/1327****************************************************************/
13511328
1329func (cs *clientSessionSuite) TestRunCallsCloserWithFalse(c *C) {
1330 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1331 c.Assert(err, IsNil)
1332 failure := errors.New("bail")
1333 has_closed := false
1334 with_false := false
1335 err = sess.run(
1336 func(b bool) { has_closed = true; with_false = !b },
1337 func() error { return failure },
1338 nil,
1339 nil,
1340 nil,
1341 nil)
1342 c.Check(err, Equals, failure)
1343 c.Check(has_closed, Equals, true)
1344 c.Check(with_false, Equals, true)
1345}
1346
1352func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) {1347func (cs *clientSessionSuite) TestRunBailsIfAuthCheckFails(c *C) {
1353 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1348 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1354 c.Assert(err, IsNil)1349 c.Assert(err, IsNil)
1355 failure := errors.New("TestRunBailsIfAuthCheckFails")1350 failure := errors.New("TestRunBailsIfAuthCheckFails")
1356 has_closed := false1351 has_closed := false
1357 err = sess.run(1352 err = sess.run(
1358 func() { has_closed = true },1353 func(bool) { has_closed = true },
1359 func() error { return failure },1354 func() error { return failure },
1360 nil,1355 nil,
1361 nil,1356 nil,
@@ -1366,12 +1361,12 @@
1366}1361}
13671362
1368func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {1363func (cs *clientSessionSuite) TestRunBailsIfHostGetterFails(c *C) {
1369 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1364 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1370 c.Assert(err, IsNil)1365 c.Assert(err, IsNil)
1371 failure := errors.New("TestRunBailsIfHostGetterFails")1366 failure := errors.New("TestRunBailsIfHostGetterFails")
1372 has_closed := false1367 has_closed := false
1373 err = sess.run(1368 err = sess.run(
1374 func() { has_closed = true },1369 func(bool) { has_closed = true },
1375 func() error { return nil },1370 func() error { return nil },
1376 func() error { return failure },1371 func() error { return failure },
1377 nil,1372 nil,
@@ -1382,11 +1377,11 @@
1382}1377}
13831378
1384func (cs *clientSessionSuite) TestRunBailsIfConnectFails(c *C) {1379func (cs *clientSessionSuite) TestRunBailsIfConnectFails(c *C) {
1385 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1380 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1386 c.Assert(err, IsNil)1381 c.Assert(err, IsNil)
1387 failure := errors.New("TestRunBailsIfConnectFails")1382 failure := errors.New("TestRunBailsIfConnectFails")
1388 err = sess.run(1383 err = sess.run(
1389 func() {},1384 func(bool) {},
1390 func() error { return nil },1385 func() error { return nil },
1391 func() error { return nil },1386 func() error { return nil },
1392 func() error { return failure },1387 func() error { return failure },
@@ -1396,11 +1391,11 @@
1396}1391}
13971392
1398func (cs *clientSessionSuite) TestRunBailsIfStartFails(c *C) {1393func (cs *clientSessionSuite) TestRunBailsIfStartFails(c *C) {
1399 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1394 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1400 c.Assert(err, IsNil)1395 c.Assert(err, IsNil)
1401 failure := errors.New("TestRunBailsIfStartFails")1396 failure := errors.New("TestRunBailsIfStartFails")
1402 err = sess.run(1397 err = sess.run(
1403 func() {},1398 func(bool) {},
1404 func() error { return nil },1399 func() error { return nil },
1405 func() error { return nil },1400 func() error { return nil },
1406 func() error { return nil },1401 func() error { return nil },
@@ -1410,16 +1405,12 @@
1410}1405}
14111406
1412func (cs *clientSessionSuite) TestRunRunsEvenIfLoopFails(c *C) {1407func (cs *clientSessionSuite) TestRunRunsEvenIfLoopFails(c *C) {
1413 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1408 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1414 c.Assert(err, IsNil)1409 c.Assert(err, IsNil)
1415 // just to make a point: until here we haven't set ErrCh & BroadcastCh (no
1416 // biggie if this stops being true)
1417 c.Check(sess.ErrCh, IsNil)
1418 c.Check(sess.BroadcastCh, IsNil)
1419 failureCh := make(chan error) // must be unbuffered1410 failureCh := make(chan error) // must be unbuffered
1420 notf := &BroadcastNotification{}1411 notf := &BroadcastNotification{}
1421 err = sess.run(1412 err = sess.run(
1422 func() {},1413 func(bool) {},
1423 func() error { return nil },1414 func() error { return nil },
1424 func() error { return nil },1415 func() error { return nil },
1425 func() error { return nil },1416 func() error { return nil },
@@ -1427,12 +1418,12 @@
1427 func() error { sess.BroadcastCh <- notf; return <-failureCh })1418 func() error { sess.BroadcastCh <- notf; return <-failureCh })
1428 c.Check(err, Equals, nil)1419 c.Check(err, Equals, nil)
1429 // if run doesn't error it sets up the channels1420 // if run doesn't error it sets up the channels
1430 c.Assert(sess.ErrCh, NotNil)1421 c.Assert(sess.errCh, NotNil)
1431 c.Assert(sess.BroadcastCh, NotNil)1422 c.Assert(sess.BroadcastCh, NotNil)
1432 c.Check(<-sess.BroadcastCh, Equals, notf)1423 c.Check(<-sess.BroadcastCh, Equals, notf)
1433 failure := errors.New("TestRunRunsEvenIfLoopFails")1424 failure := errors.New("TestRunRunsEvenIfLoopFails")
1434 failureCh <- failure1425 failureCh <- failure
1435 c.Check(<-sess.ErrCh, Equals, failure)1426 c.Check(<-sess.errCh, Equals, failure)
1436 // so now you know it was running in a goroutine :)1427 // so now you know it was running in a goroutine :)
1437}1428}
14381429
@@ -1441,7 +1432,7 @@
1441****************************************************************/1432****************************************************************/
14421433
1443func (cs *clientSessionSuite) TestJitter(c *C) {1434func (cs *clientSessionSuite) TestJitter(c *C) {
1444 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1435 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1445 c.Assert(err, IsNil)1436 c.Assert(err, IsNil)
1446 num_tries := 20 // should do the math1437 num_tries := 20 // should do the math
1447 spread := time.Second //1438 spread := time.Second //
@@ -1473,20 +1464,23 @@
14731464
1474func (cs *clientSessionSuite) TestDialPanics(c *C) {1465func (cs *clientSessionSuite) TestDialPanics(c *C) {
1475 // one last unhappy test1466 // one last unhappy test
1476 sess, err := NewSession("", dummyConf, "wah", cs.lvls, cs.log)1467 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1477 c.Assert(err, IsNil)1468 c.Assert(err, IsNil)
1478 sess.Protocolator = nil1469 sess.Protocolator = nil
1479 c.Check(sess.Dial, PanicMatches, ".*protocol constructor.")1470 c.Check(sess.Dial, PanicMatches, ".*protocol constructor.")
1480}1471}
14811472
1482var (1473var (
1483 dialTestTimeout = 100 * time.Millisecond1474 dialTestTimeout = 300 * time.Millisecond
1484 dialTestConf = ClientSessionConfig{
1485 ExchangeTimeout: dialTestTimeout,
1486 PEM: helpers.TestCertPEMBlock,
1487 }
1488)1475)
14891476
1477func dialTestConf() ClientSessionConfig {
1478 conf := dummyConf()
1479 conf.ExchangeTimeout = dialTestTimeout
1480 conf.PEM = helpers.TestCertPEMBlock
1481 return conf
1482}
1483
1490func (cs *clientSessionSuite) TestDialBadServerName(c *C) {1484func (cs *clientSessionSuite) TestDialBadServerName(c *C) {
1491 // a borked server name1485 // a borked server name
1492 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)1486 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)
@@ -1505,7 +1499,7 @@
1505 }))1499 }))
1506 defer ts.Close()1500 defer ts.Close()
15071501
1508 sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)1502 sess, err := NewSession(ts.URL, dialTestConf(), "wah", cs.lvls, cs.log)
1509 c.Assert(err, IsNil)1503 c.Assert(err, IsNil)
1510 tconn := &testConn{}1504 tconn := &testConn{}
1511 sess.Connection = tconn1505 sess.Connection = tconn
@@ -1550,7 +1544,7 @@
1550 }))1544 }))
1551 defer ts.Close()1545 defer ts.Close()
15521546
1553 sess, err := NewSession(ts.URL, dialTestConf, "wah", cs.lvls, cs.log)1547 sess, err := NewSession(ts.URL, dialTestConf(), "wah", cs.lvls, cs.log)
1554 c.Assert(err, IsNil)1548 c.Assert(err, IsNil)
1555 tconn := &testConn{CloseCondition: condition.Fail2Work(10)}1549 tconn := &testConn{CloseCondition: condition.Fail2Work(10)}
1556 sess.Connection = tconn1550 sess.Connection = tconn
@@ -1584,7 +1578,7 @@
15841578
1585 // 2. "connect" (but on the fake protcol above! woo)1579 // 2. "connect" (but on the fake protcol above! woo)
15861580
1587 c.Check(takeNext(downCh), Equals, "deadline 100ms")1581 c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout))
1588 _, ok := takeNext(downCh).(protocol.ConnectMsg)1582 _, ok := takeNext(downCh).(protocol.ConnectMsg)
1589 c.Check(ok, Equals, true)1583 c.Check(ok, Equals, true)
1590 upCh <- nil // no error1584 upCh <- nil // no error
@@ -1597,7 +1591,7 @@
1597 // 3. "loop"1591 // 3. "loop"
15981592
1599 // ping works,1593 // ping works,
1600 c.Check(takeNext(downCh), Equals, "deadline 110ms")1594 c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout+10*time.Millisecond))
1601 upCh <- protocol.PingPongMsg{Type: "ping"}1595 upCh <- protocol.PingPongMsg{Type: "ping"}
1602 c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"})1596 c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"})
1603 upCh <- nil1597 upCh <- nil
@@ -1613,7 +1607,7 @@
1613 TopLevel: 2,1607 TopLevel: 2,
1614 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},1608 Payloads: []json.RawMessage{json.RawMessage(`{"b":1}`)},
1615 }1609 }
1616 c.Check(takeNext(downCh), Equals, "deadline 110ms")1610 c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout+10*time.Millisecond))
1617 upCh <- b1611 upCh <- b
1618 c.Check(takeNext(downCh), Equals, protocol.AckMsg{"ack"})1612 c.Check(takeNext(downCh), Equals, protocol.AckMsg{"ack"})
1619 upCh <- nil1613 upCh <- nil
@@ -1625,21 +1619,21 @@
1625 c.Check(levels, DeepEquals, map[string]int64{"0": 2})1619 c.Check(levels, DeepEquals, map[string]int64{"0": 2})
16261620
1627 // and ping still work even after that.1621 // and ping still work even after that.
1628 c.Check(takeNext(downCh), Equals, "deadline 110ms")1622 c.Check(takeNext(downCh), Equals, fmt.Sprintf("deadline %v", dialTestTimeout+10*time.Millisecond))
1629 upCh <- protocol.PingPongMsg{Type: "ping"}1623 upCh <- protocol.PingPongMsg{Type: "ping"}
1630 c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"})1624 c.Check(takeNext(downCh), Equals, protocol.PingPongMsg{Type: "pong"})
1631 failure := errors.New("pongs")1625 failure := errors.New("pongs")
1632 upCh <- failure1626 upCh <- failure
1633 c.Check(<-sess.ErrCh, Equals, failure)1627 c.Check(<-sess.errCh, Equals, failure)
1634}1628}
16351629
1636func (cs *clientSessionSuite) TestDialWorksDirect(c *C) {1630func (cs *clientSessionSuite) TestDialWorksDirect(c *C) {
1637 // happy path thoughts1631 // happy path thoughts
1638 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)1632 lst, err := tls.Listen("tcp", "localhost:0", helpers.TestTLSServerConfig)
1639 c.Assert(err, IsNil)1633 c.Assert(err, IsNil)
1640 sess, err := NewSession(lst.Addr().String(), dialTestConf, "wah", cs.lvls, cs.log)1634 sess, err := NewSession(lst.Addr().String(), dialTestConf(), "wah", cs.lvls, cs.log)
1641 c.Assert(err, IsNil)1635 c.Assert(err, IsNil)
1642 defer sess.Close()1636 defer sess.StopKeepConnection()
16431637
1644 upCh := make(chan interface{}, 5)1638 upCh := make(chan interface{}, 5)
1645 downCh := make(chan interface{}, 5)1639 downCh := make(chan interface{}, 5)
@@ -1658,7 +1652,7 @@
1658****************************************************************/1652****************************************************************/
16591653
1660func (cs *clientSessionSuite) TestShouldDelay(c *C) {1654func (cs *clientSessionSuite) TestShouldDelay(c *C) {
1661 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)1655 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1662 c.Assert(err, IsNil)1656 c.Assert(err, IsNil)
1663 c.Check(sess.ShouldDelay(), Equals, false)1657 c.Check(sess.ShouldDelay(), Equals, false)
1664 sess.setShouldDelay()1658 sess.setShouldDelay()
@@ -1668,7 +1662,7 @@
1668}1662}
16691663
1670func (cs *clientSessionSuite) TestRedialDelay(c *C) {1664func (cs *clientSessionSuite) TestRedialDelay(c *C) {
1671 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)1665 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1672 c.Assert(err, IsNil)1666 c.Assert(err, IsNil)
1673 sess.redialDelays = []time.Duration{17, 42}1667 sess.redialDelays = []time.Duration{17, 42}
1674 n := 01668 n := 0
@@ -1689,15 +1683,207 @@
1689}1683}
16901684
1691/****************************************************************1685/****************************************************************
1692 ClearCookie() tests1686 ResetCookie() tests
1693****************************************************************/1687****************************************************************/
16941688
1695func (cs *clientSessionSuite) TestClearCookie(c *C) {1689func (cs *clientSessionSuite) TestResetCookie(c *C) {
1696 sess, err := NewSession("foo:443", dummyConf, "", cs.lvls, cs.log)1690 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1697 c.Assert(err, IsNil)1691 c.Assert(err, IsNil)
1692 c.Assert(sess.KeepConnection(), IsNil)
1693 defer sess.StopKeepConnection()
1698 c.Check(sess.getCookie(), Equals, "")1694 c.Check(sess.getCookie(), Equals, "")
1699 sess.setCookie("COOKIE")1695 sess.setCookie("COOKIE")
1700 c.Check(sess.getCookie(), Equals, "COOKIE")1696 c.Check(sess.getCookie(), Equals, "COOKIE")
1701 sess.ClearCookie()1697 sess.ResetCookie()
1702 c.Check(sess.getCookie(), Equals, "")1698 c.Check(sess.getCookie(), Equals, "")
1703}1699}
1700
1701/****************************************************************
1702 KeepConnection() (and related) tests
1703****************************************************************/
1704
1705func (cs *clientSessionSuite) TestKeepConnectionDoesNothingIfNotConnected(c *C) {
1706 // how do you test "does nothing?"
1707 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1708 c.Assert(err, IsNil)
1709 c.Assert(sess, NotNil)
1710 c.Assert(sess.State(), Equals, Pristine)
1711 c.Assert(sess.KeepConnection(), IsNil)
1712 defer sess.StopKeepConnection()
1713 // stopCh is meant to be used just for closing it, but abusing
1714 // it for testing seems the right thing to do: this ensures
1715 // the thing is ticking along before we check the state of
1716 // stuff.
1717 sess.stopCh <- struct{}{}
1718 c.Check(sess.State(), Equals, Disconnected)
1719}
1720
1721func (cs *clientSessionSuite) TestYouCantCallKeepConnectionTwice(c *C) {
1722 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1723 c.Assert(err, IsNil)
1724 c.Assert(sess, NotNil)
1725 c.Assert(sess.State(), Equals, Pristine)
1726 c.Assert(sess.KeepConnection(), IsNil)
1727 defer sess.StopKeepConnection()
1728 c.Check(sess.KeepConnection(), NotNil)
1729}
1730
1731func (cs *clientSessionSuite) TestStopKeepConnectionShutsdown(c *C) {
1732 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1733 c.Assert(err, IsNil)
1734 c.Assert(sess, NotNil)
1735 sess.StopKeepConnection()
1736 c.Check(sess.State(), Equals, Shutdown)
1737}
1738
1739func (cs *clientSessionSuite) TestHasConnectivityTriggersConnectivityHandler(c *C) {
1740 sess, err := NewSession("foo:443", dummyConf(), "", cs.lvls, cs.log)
1741 c.Assert(err, IsNil)
1742 c.Assert(sess, NotNil)
1743 testCh := make(chan bool)
1744 sess.connHandler = func(p bool) { testCh <- p }
1745 go sess.doKeepConnection()
1746 defer sess.StopKeepConnection()
1747 sess.HasConnectivity(true)
1748 c.Check(<-testCh, Equals, true)
1749 sess.HasConnectivity(false)
1750 c.Check(<-testCh, Equals, false)
1751}
1752
1753func (cs *clientSessionSuite) TestDoneChIsEmptiedAndLogged(c *C) {
1754 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1755 c.Assert(err, IsNil)
1756 sess.doneCh = make(chan uint32) // unbuffered
1757
1758 sess.KeepConnection()
1759 defer sess.StopKeepConnection()
1760
1761 sess.doneCh <- 23
1762 sess.doneCh <- 24 // makes sure the first one has been processed before checking
1763
1764 c.Check(cs.log.Captured(),
1765 Matches, `(?ms).* connected after 23 attempts\.`)
1766}
1767
1768func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedAndAutoRedial(c *C) {
1769 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1770 c.Assert(err, IsNil)
1771 ch := make(chan struct{}, 1)
1772 sess.errCh = make(chan error) // unbuffered
1773 sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
1774 sess.lastConn = true // -> autoRedial, if the session is in Disconnected
1775
1776 sess.KeepConnection()
1777 defer sess.StopKeepConnection()
1778
1779 sess.setState(Error)
1780 sess.errCh <- errors.New("potato")
1781 select {
1782 case <-ch:
1783 // all ok
1784 case <-time.After(100 * time.Millisecond):
1785 c.Fatalf("redialDelay not called (-> autoRedial not called)?")
1786 }
1787
1788 c.Check(cs.log.Captured(),
1789 Matches, `(?ms).* session error.*potato`)
1790}
1791
1792func (cs *clientSessionSuite) TestErrChIsEmptiedAndLoggedNoAutoRedial(c *C) {
1793 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1794 c.Assert(err, IsNil)
1795 ch := make(chan struct{}, 1)
1796 sess.errCh = make(chan error) // unbuffered
1797 sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
1798 sess.connHandler = func(bool) {}
1799 sess.lastConn = false // so, no autoredial
1800
1801 sess.KeepConnection()
1802 defer sess.StopKeepConnection()
1803
1804 sess.errCh <- errors.New("potato")
1805 c.Assert(sess.State(), Equals, Disconnected)
1806 select {
1807 case <-ch:
1808 c.Fatalf("redialDelay called (-> autoRedial called) when disconnected?")
1809 case <-time.After(100 * time.Millisecond):
1810 // all ok
1811 }
1812
1813 c.Check(cs.log.Captured(),
1814 Matches, `(?ms).* session error.*potato`)
1815}
1816
1817func (cs *clientSessionSuite) TestHandleConnConnFromConnected(c *C) {
1818 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1819 c.Assert(err, IsNil)
1820 ch := make(chan struct{}, 1)
1821 sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
1822 sess.state = Connected
1823 sess.lastConn = true
1824 sess.handleConn(true)
1825 c.Check(sess.lastConn, Equals, true)
1826
1827 select {
1828 case <-ch:
1829 // all ok
1830 case <-time.After(100 * time.Millisecond):
1831 c.Fatalf("redialDelay not called (-> autoRedial not called)?")
1832 }
1833}
1834
1835func (cs *clientSessionSuite) TestHandleConnConnFromDisconnected(c *C) {
1836 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1837 c.Assert(err, IsNil)
1838 ch := make(chan struct{}, 1)
1839 sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
1840 sess.state = Disconnected
1841 sess.lastConn = false
1842 sess.handleConn(true)
1843 c.Check(sess.lastConn, Equals, true)
1844
1845 select {
1846 case <-ch:
1847 // all ok
1848 case <-time.After(100 * time.Millisecond):
1849 c.Fatalf("redialDelay not called (-> autoRedial not called)?")
1850 }
1851}
1852
1853func (cs *clientSessionSuite) TestHandleConnNotConnFromDisconnected(c *C) {
1854 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1855 c.Assert(err, IsNil)
1856 ch := make(chan struct{}, 1)
1857 sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
1858 sess.state = Disconnected
1859 sess.lastConn = false
1860 sess.handleConn(false)
1861 c.Check(sess.lastConn, Equals, false)
1862
1863 select {
1864 case <-ch:
1865 c.Fatalf("redialDelay called (-> autoRedial called)?")
1866 case <-time.After(100 * time.Millisecond):
1867 // all ok
1868 }
1869 c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`)
1870}
1871
1872func (cs *clientSessionSuite) TestHandleConnNotConnFromConnected(c *C) {
1873 sess, err := NewSession("", dummyConf(), "wah", cs.lvls, cs.log)
1874 c.Assert(err, IsNil)
1875 ch := make(chan struct{}, 1)
1876 sess.redialDelay = func(sess *clientSession) time.Duration { ch <- struct{}{}; return 0 }
1877 sess.state = Connected
1878 sess.lastConn = true
1879 sess.handleConn(false)
1880 c.Check(sess.lastConn, Equals, false)
1881
1882 select {
1883 case <-ch:
1884 c.Fatalf("redialDelay called (-> autoRedial called)?")
1885 case <-time.After(100 * time.Millisecond):
1886 // all ok
1887 }
1888 c.Check(cs.log.Captured(), Matches, `(?ms).*-> Disconnected`)
1889}
17041890
=== added file 'docs/Makefile'
--- docs/Makefile 1970-01-01 00:00:00 +0000
+++ docs/Makefile 2015-03-26 16:42:21 +0000
@@ -0,0 +1,4 @@
1all: *txt *svg
2 rst2html --link-stylesheet highlevel.txt highlevel.html
3 rst2html --link-stylesheet lowlevel.txt lowlevel.html
4
05
=== modified file 'docs/_common.txt'
--- docs/_common.txt 2014-09-05 14:49:44 +0000
+++ docs/_common.txt 2015-03-26 16:42:21 +0000
@@ -7,46 +7,22 @@
7The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed7The helper receives two arguments ``infile`` and ``outfile``. The message is delivered via ``infile`` and the transformed
8version is placed in ``outfile``.8version is placed in ``outfile``.
99
10This is the simplest possible useful helper, which simply passes the message through unchanged::10This is the simplest possible useful helper, which simply passes the message through unchanged:
1111
12 #!/usr/bin/python312.. include:: example-client/helloHelper
1313 :literal:
14 import sys14
15 f1, f2 = sys.argv[1:3]15Helpers need to be added to the click package manifest:
16 open(f2, "w").write(open(f1).read())16
1717.. include:: example-client/manifest.json
18Helpers need to be added to the click package manifest::18 :literal:
19
20 {
21 "name": "com.ubuntu.developer.ralsina.hello",
22 "description": "description of hello",
23 "framework": "ubuntu-sdk-14.10-qml-dev2",
24 "architecture": "all",
25 "title": "hello",
26 "hooks": {
27 "hello": {
28 "apparmor": "hello.json",
29 "desktop": "hello.desktop"
30 },
31 "helloHelper": {
32 "apparmor": "helloHelper-apparmor.json",
33 "push-helper": "helloHelper.json"
34 }
35 },
36 "version": "0.2",
37 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>"
38 }
3919
40Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.20Here, we created a helloHelper entry in hooks that has an apparmor profile and an additional JSON file for the push-helper hook.
4121
42helloHelper-apparmor.json must contain **only** the push-notification-client policy group::22helloHelper-apparmor.json must contain **only** the push-notification-client policy group and the ubuntu-push-helper template:
4323
44 {24.. include:: example-client/helloHelper-apparmor.json
45 "policy_groups": [25 :literal:
46 "push-notification-client"
47 ],
48 "policy_version": 1.2
49 }
5026
51And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally27And helloHelper.json must have at least a exec key with the path to the helper executable relative to the json, and optionally
52an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).28an app_id key containing the short id of one of the apps in the package (in the format packagename_appname without a version).
@@ -138,15 +114,10 @@
138Security114Security
139~~~~~~~~115~~~~~~~~
140116
141To use the push API, applications need to request permission in their security profile, using something like this::117To use the push API, applications need to request permission in their security profile, using something like this:
142118
143 {119.. include:: example-client/hello.json
144 "policy_groups": [120 :literal:
145 "networking",
146 "push-notification-client"
147 ],
148 "policy_version": 1.2
149 }
150121
151122
152Ubuntu Push Server API123Ubuntu Push Server API
@@ -184,3 +155,46 @@
184:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.155:clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
185:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.156:replace_tag: If there's a pending notification with the same tag, delete it before queuing this new one.
186:data: A JSON object.157:data: A JSON object.
158
159Limitations of the Server API
160-----------------------------
161
162The push notification infrastructure is meant to help ensuring timely
163delivery of application notifications if the device is online or
164timely informing the device user about application notifications that
165were pending when the device comes back online. This in the face of
166applications not being allowed to be running all the time, and
167avoiding the resource cost of many applications all polling different services
168frequently.
169
170The push notification infrastructure is architected to guarantee at
171least best-effort with respect to these goals and beyond it, on the
172other end applications should not expect to be able to use and only
173rely on the push notification infrastructure to store application
174messages if they want ensure all their notification or messages are
175delivered, the infrastructure is not intended to be the only long term
176"inbox" storage for an application.
177
178To preserve overall throughput the infrastructure imposes some limits
179on applications:
180
181 * message data payload is limited to 2K
182
183 * when inserted all messages need to specify an expiration date after
184 which they can be dropped and not delivered
185
186 * an application is limited in the number of messages per token
187 (application/user/device combination) that can be undelivered/pending at the
188 same time (100 currently)
189
190replace_tag can be used to implement notifications for which the newest
191one replace the previous one if pending.
192
193clear_pending can be used to be deal with a pending message limit
194reached, possibly substituting the current undelivered messages with a
195more generic one.
196
197Applications using the push notification HTTP API should be robust
198against receiving 503 errors, retrying after waiting with increasing
199back-off. Later rate limits (signaled with the 429 status) may also come
200into play.
187201
=== modified file 'docs/example-client/components/ChatClient.qml'
--- docs/example-client/components/ChatClient.qml 2014-09-05 14:40:39 +0000
+++ docs/example-client/components/ChatClient.qml 2015-03-26 16:42:21 +0000
@@ -60,8 +60,8 @@
60 if (options["enabled"]) {60 if (options["enabled"]) {
61 data["data"]["notification"] = {61 data["data"]["notification"] = {
62 "card": {62 "card": {
63 "summary": nick + " says: " + message["message"],63 "summary": nick + " says:",
64 "body": "",64 "body": message["message"],
65 "popup": options["popup"],65 "popup": options["popup"],
66 "persist": options["persist"],66 "persist": options["persist"],
67 "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"]67 "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"]
6868
=== modified file 'docs/example-client/helloHelper-apparmor.json'
--- docs/example-client/helloHelper-apparmor.json 2014-09-05 14:40:39 +0000
+++ docs/example-client/helloHelper-apparmor.json 2015-03-26 16:42:21 +0000
@@ -1,4 +1,5 @@
1{1{
2 "template": "ubuntu-push-helper",
2 "policy_groups": [3 "policy_groups": [
3 "push-notification-client"4 "push-notification-client"
4 ],5 ],
56
=== modified file 'docs/example-client/main.qml'
--- docs/example-client/main.qml 2014-09-10 14:38:40 +0000
+++ docs/example-client/main.qml 2015-03-26 16:42:21 +0000
@@ -26,9 +26,42 @@
26 property alias nickEnabled: nickEdit.enabled26 property alias nickEnabled: nickEdit.enabled
27 }27 }
2828
29 states: [
30 State {
31 name: "no-push-token"
32 when: (pushClient.token == "")
33 PropertyChanges { target: nickEdit; readOnly: true}
34 PropertyChanges { target: nickEdit; focus: true}
35 PropertyChanges { target: messageEdit; enabled: false}
36 PropertyChanges { target: loginButton; enabled: false}
37 PropertyChanges { target: loginButton; text: "Login"}
38 },
39 State {
40 name: "push-token-not-registered"
41 when: ((pushClient.token != "") && (chatClient.registered == false))
42 PropertyChanges { target: nickEdit; readOnly: false}
43 PropertyChanges { target: nickEdit; text: ""}
44 PropertyChanges { target: nickEdit; focus: true}
45 PropertyChanges { target: messageEdit; enabled: false}
46 PropertyChanges { target: loginButton; enabled: true}
47 PropertyChanges { target: loginButton; text: "Login"}
48 },
49 State {
50 name: "registered"
51 when: ((pushClient.token != "") && (chatClient.registered == true))
52 PropertyChanges { target: nickEdit; readOnly: true}
53 PropertyChanges { target: nickEdit; text: "Your nick is " + chatClient.nick}
54 PropertyChanges { target: messageEdit; focus: true}
55 PropertyChanges { target: messageEdit; enabled: true}
56 PropertyChanges { target: loginButton; enabled: true}
57 PropertyChanges { target: loginButton; text: "Logout"}
58 }
59 ]
60
61 state: "no-push-token"
62
29 ChatClient {63 ChatClient {
30 id: chatClient64 id: chatClient
31 onRegisteredChanged: {nickEdit.registered()}
32 onError: {messageList.handle_error(msg)}65 onError: {messageList.handle_error(msg)}
33 token: pushClient.token66 token: pushClient.token
34 }67 }
@@ -38,13 +71,16 @@
38 Component.onCompleted: {71 Component.onCompleted: {
39 notificationsChanged.connect(messageList.handle_notifications)72 notificationsChanged.connect(messageList.handle_notifications)
40 error.connect(messageList.handle_error)73 error.connect(messageList.handle_error)
74 onTokenChanged: {
75 console.log("foooooo")
76 }
41 }77 }
42 appId: "com.ubuntu.developer.ralsina.hello_hello"78 appId: "com.ubuntu.developer.ralsina.hello_hello"
79
43 }80 }
4481
45 TextField {82 TextField {
46 id: nickEdit83 id: nickEdit
47 focus: true
48 placeholderText: "Your nickname"84 placeholderText: "Your nickname"
49 inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase85 inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase
50 anchors.left: parent.left86 anchors.left: parent.left
@@ -53,31 +89,17 @@
53 anchors.leftMargin: units.gu(.5)89 anchors.leftMargin: units.gu(.5)
54 anchors.rightMargin: units.gu(1)90 anchors.rightMargin: units.gu(1)
55 anchors.topMargin: units.gu(.5)91 anchors.topMargin: units.gu(.5)
56 function registered() {
57 readOnly = true
58 text = "Your nick is " + chatClient.nick
59 messageEdit.focus = true
60 messageEdit.enabled = true
61 loginButton.text = "Logout"
62 }
63 onAccepted: { loginButton.clicked() }92 onAccepted: { loginButton.clicked() }
64 }93 }
6594
66 Button {95 Button {
67 id: loginButton96 id: loginButton
68 text: chatClient.rgistered? "Logout": "Login"
69 anchors.top: nickEdit.top97 anchors.top: nickEdit.top
70 anchors.right: parent.right98 anchors.right: parent.right
71 anchors.rightMargin: units.gu(.5)99 anchors.rightMargin: units.gu(.5)
72 onClicked: {100 onClicked: {
73 if (chatClient.nick) { // logout101 if (chatClient.nick) { // logout
74 chatClient.nick = ""102 chatClient.nick = ""
75 text = "Login"
76 nickEdit.enabled = true
77 nickEdit.readOnly = false
78 nickEdit.text = ""
79 nickEdit.focus = true
80 messageEdit.enabled = false
81 } else { // login103 } else { // login
82 chatClient.nick = nickEdit.text104 chatClient.nick = nickEdit.text
83 }105 }
@@ -94,7 +116,6 @@
94 anchors.rightMargin: units.gu(.5)116 anchors.rightMargin: units.gu(.5)
95 anchors.leftMargin: units.gu(.5)117 anchors.leftMargin: units.gu(.5)
96 placeholderText: "Your message"118 placeholderText: "Your message"
97 enabled: false
98 onAccepted: {119 onAccepted: {
99 console.log("sending " + text)120 console.log("sending " + text)
100 var idx = text.indexOf(":")121 var idx = text.indexOf(":")
@@ -210,7 +231,7 @@
210 right: parent.right231 right: parent.right
211 bottom: parent.bottom232 bottom: parent.bottom
212 }233 }
213 height: item1.height * 7234 height: item1.height * 9
214 UbuntuShape {235 UbuntuShape {
215 anchors.fill: parent236 anchors.fill: parent
216 color: Theme.palette.normal.overlay237 color: Theme.palette.normal.overlay
@@ -268,6 +289,14 @@
268 value: 42289 value: 42
269 }290 }
270 }291 }
292 Button {
293 text: "Set Counter Via Plugin"
294 onClicked: { pushClient.count = counterSlider.value; }
295 }
296 Button {
297 text: "Clear Persistent Notifications"
298 onClicked: { pushClient.clearPersistent([]); }
299 }
271 }300 }
272 }301 }
273 }302 }
274303
=== modified file 'docs/example-client/manifest.json'
--- docs/example-client/manifest.json 2014-09-10 14:38:31 +0000
+++ docs/example-client/manifest.json 2015-03-26 16:42:21 +0000
@@ -1,7 +1,7 @@
1{1{
2 "architecture": "all",2 "architecture": "all",
3 "description": "Example app for Ubuntu push notifications.",3 "description": "Example app for Ubuntu push notifications.",
4 "framework": "ubuntu-sdk-14.10-dev2",4 "framework": "ubuntu-sdk-14.10",
5 "hooks": {5 "hooks": {
6 "hello": {6 "hello": {
7 "apparmor": "hello.json",7 "apparmor": "hello.json",
@@ -15,5 +15,5 @@
15 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>",15 "maintainer": "Roberto Alsina <roberto.alsina@canonical.com>",
16 "name": "com.ubuntu.developer.ralsina.hello",16 "name": "com.ubuntu.developer.ralsina.hello",
17 "title": "Hello",17 "title": "Hello",
18 "version": "0.4.2"18 "version": "0.4.4"
19}19}
2020
=== modified file 'docs/example-server/app.js'
--- docs/example-server/app.js 2014-09-05 14:57:17 +0000
+++ docs/example-server/app.js 2015-03-26 16:42:21 +0000
@@ -182,22 +182,40 @@
182 */182 */
183 if (cfg.play_notify_form) {183 if (cfg.play_notify_form) {
184 app.post("/play-notify-form", function(req, resp) {184 app.post("/play-notify-form", function(req, resp) {
185 resp.type('text/plain')185 if (!req.body.message||!req.body.nick) {
186 if (!req.body.data||!req.body.nick) {
187 resp.send(400, "invalid/empty fields\n")
188 return
189 }
190 var data
191 try {
192 data = JSON.parse(req.body.data)
193 } catch(e) {
194 resp.send(400, "data is not JSON\n")
195 return
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches