Merge lp:~chipaca/ubuntu-push/persistence into lp:ubuntu-push
- persistence
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | John Lenton |
Approved revision: | 67 |
Merged at revision: | 79 |
Proposed branch: | lp:~chipaca/ubuntu-push/persistence |
Merge into: | lp:ubuntu-push |
Prerequisite: | lp:~chipaca/ubuntu-push/sqlevelmap-in-session |
Diff against target: |
484 lines (+91/-51) 2 files modified
client/client.go (+28/-17) client/client_test.go (+63/-34) |
To merge this branch: | bzr merge lp:~chipaca/ubuntu-push/persistence |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Samuele Pedroni | Approve | ||
Review via email:
|
Commit message
and this gives the client the ability to persist its session's levelmap
Description of the change
With this, NewPushClient takes a second argument that is the path to
the sqlite db in which to persist its level map. If the path is "",
use the good ol' mapLevelMap instead.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~chipaca/ubuntu-push/persistence into lp:ubuntu-push failed. Below is the output from the failed tests.
mkdir -p /mnt/tarmac/
mkdir -p /mnt/tarmac/
go get -u launchpad.
go get -d -u launchpad.
/mnt/tarmac/
"/mnt/tarmac/
go install launchpad.
go test launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
go test -race launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
Preview Diff
1 | === modified file 'client/client.go' |
2 | --- client/client.go 2014-02-21 16:17:28 +0000 |
3 | +++ client/client.go 2014-03-12 11:09:23 +0000 |
4 | @@ -50,8 +50,9 @@ |
5 | LogLevel string `json:"log_level"` |
6 | } |
7 | |
8 | -// pushClient is the Ubuntu Push Notifications client-side daemon. |
9 | -type pushClient struct { |
10 | +// PushClient is the Ubuntu Push Notifications client-side daemon. |
11 | +type PushClient struct { |
12 | + leveldbPath string |
13 | configPath string |
14 | config ClientConfig |
15 | log logger.Logger |
16 | @@ -70,15 +71,16 @@ |
17 | |
18 | // Creates a new Ubuntu Push Notifications client-side daemon that will use |
19 | // the given configuration file. |
20 | -func NewPushClient(configPath string) *pushClient { |
21 | - client := new(pushClient) |
22 | +func NewPushClient(configPath string, leveldbPath string) *PushClient { |
23 | + client := new(PushClient) |
24 | client.configPath = configPath |
25 | + client.leveldbPath = leveldbPath |
26 | |
27 | return client |
28 | } |
29 | |
30 | // configure loads its configuration, and sets it up. |
31 | -func (client *pushClient) configure() error { |
32 | +func (client *PushClient) configure() error { |
33 | f, err := os.Open(client.configPath) |
34 | if err != nil { |
35 | return fmt.Errorf("opening config: %v", err) |
36 | @@ -115,7 +117,7 @@ |
37 | } |
38 | |
39 | // getDeviceId gets the whoopsie identifier for the device |
40 | -func (client *pushClient) getDeviceId() error { |
41 | +func (client *PushClient) getDeviceId() error { |
42 | err := client.idder.Generate() |
43 | if err != nil { |
44 | return err |
45 | @@ -125,7 +127,7 @@ |
46 | } |
47 | |
48 | // takeTheBus starts the connection(s) to D-Bus and sets up associated event channels |
49 | -func (client *pushClient) takeTheBus() error { |
50 | +func (client *PushClient) takeTheBus() error { |
51 | go connectivity.ConnectedState(client.connectivityEndp, |
52 | client.config.ConnectivityConfig, client.log, client.connCh) |
53 | iniCh := make(chan uint32) |
54 | @@ -140,10 +142,10 @@ |
55 | } |
56 | |
57 | // initSession creates the session object |
58 | -func (client *pushClient) initSession() error { |
59 | +func (client *PushClient) initSession() error { |
60 | sess, err := session.NewSession(string(client.config.Addr), client.pem, |
61 | client.config.ExchangeTimeout.Duration, client.deviceId, |
62 | - levelmap.NewLevelMap, client.log) |
63 | + client.levelMapFactory, client.log) |
64 | if err != nil { |
65 | return err |
66 | } |
67 | @@ -151,8 +153,17 @@ |
68 | return nil |
69 | } |
70 | |
71 | +// levelmapFactory returns a levelMap for the session |
72 | +func (client *PushClient) levelMapFactory() (levelmap.LevelMap, error) { |
73 | + if client.leveldbPath == "" { |
74 | + return levelmap.NewLevelMap() |
75 | + } else { |
76 | + return levelmap.NewSqliteLevelMap(client.leveldbPath) |
77 | + } |
78 | +} |
79 | + |
80 | // handleConnState deals with connectivity events |
81 | -func (client *pushClient) handleConnState(hasConnectivity bool) { |
82 | +func (client *PushClient) handleConnState(hasConnectivity bool) { |
83 | if client.hasConnectivity == hasConnectivity { |
84 | // nothing to do! |
85 | return |
86 | @@ -166,7 +177,7 @@ |
87 | } |
88 | |
89 | // handleErr deals with the session erroring out of its loop |
90 | -func (client *pushClient) handleErr(err error) { |
91 | +func (client *PushClient) handleErr(err error) { |
92 | // if we're not connected, we don't really care |
93 | client.log.Errorf("session exited: %s", err) |
94 | if client.hasConnectivity { |
95 | @@ -175,7 +186,7 @@ |
96 | } |
97 | |
98 | // handleNotification deals with receiving a notification |
99 | -func (client *pushClient) handleNotification() error { |
100 | +func (client *PushClient) handleNotification() error { |
101 | action_id := "dummy_id" |
102 | a := []string{action_id, "Go get it!"} // action value not visible on the phone |
103 | h := map[string]*dbus.Variant{"x-canonical-switch-to-application": &dbus.Variant{true}} |
104 | @@ -199,14 +210,14 @@ |
105 | } |
106 | |
107 | // handleClick deals with the user clicking a notification |
108 | -func (client *pushClient) handleClick() error { |
109 | +func (client *PushClient) handleClick() error { |
110 | // it doesn't get much simpler... |
111 | urld := urldispatcher.New(client.urlDispatcherEndp, client.log) |
112 | return urld.DispatchURL("settings:///system/system-update") |
113 | } |
114 | |
115 | // doLoop connects events with their handlers |
116 | -func (client *pushClient) doLoop(connhandler func(bool), clickhandler, notifhandler func() error, errhandler func(error)) { |
117 | +func (client *PushClient) doLoop(connhandler func(bool), clickhandler, notifhandler func() error, errhandler func(error)) { |
118 | for { |
119 | select { |
120 | case state := <-client.connCh: |
121 | @@ -225,7 +236,7 @@ |
122 | |
123 | // doStart calls each of its arguments in order, returning the first non-nil |
124 | // error (or nil at the end) |
125 | -func (client *pushClient) doStart(fs ...func() error) error { |
126 | +func (client *PushClient) doStart(fs ...func() error) error { |
127 | for _, f := range fs { |
128 | if err := f(); err != nil { |
129 | return err |
130 | @@ -235,13 +246,13 @@ |
131 | } |
132 | |
133 | // Loop calls doLoop with the "real" handlers |
134 | -func (client *pushClient) Loop() { |
135 | +func (client *PushClient) Loop() { |
136 | client.doLoop(client.handleConnState, client.handleClick, |
137 | client.handleNotification, client.handleErr) |
138 | } |
139 | |
140 | // Start calls doStart with the "real" starters |
141 | -func (client *pushClient) Start() error { |
142 | +func (client *PushClient) Start() error { |
143 | return client.doStart( |
144 | client.configure, |
145 | client.getDeviceId, |
146 | |
147 | === modified file 'client/client_test.go' |
148 | --- client/client_test.go 2014-02-08 18:26:49 +0000 |
149 | +++ client/client_test.go 2014-03-12 11:09:23 +0000 |
150 | @@ -52,9 +52,10 @@ |
151 | } |
152 | |
153 | type clientSuite struct { |
154 | - timeouts []time.Duration |
155 | - configPath string |
156 | - log *helpers.TestLogger |
157 | + timeouts []time.Duration |
158 | + configPath string |
159 | + leveldbPath string |
160 | + log *helpers.TestLogger |
161 | } |
162 | |
163 | var _ = Suite(&clientSuite{}) |
164 | @@ -74,6 +75,7 @@ |
165 | |
166 | func (cs *clientSuite) SetUpSuite(c *C) { |
167 | cs.timeouts = util.SwapTimeouts([]time.Duration{0}) |
168 | + cs.leveldbPath = "" |
169 | } |
170 | |
171 | func (cs *clientSuite) TearDownSuite(c *C) { |
172 | @@ -99,12 +101,21 @@ |
173 | ioutil.WriteFile(cs.configPath, []byte(cfg), 0600) |
174 | } |
175 | |
176 | +type sqlientSuite struct{ clientSuite } |
177 | + |
178 | +func (s *sqlientSuite) SetUpSuite(c *C) { |
179 | + s.clientSuite.SetUpSuite(c) |
180 | + s.leveldbPath = ":memory:" |
181 | +} |
182 | + |
183 | +var _ = Suite(&sqlientSuite{}) |
184 | + |
185 | /***************************************************************** |
186 | configure tests |
187 | ******************************************************************/ |
188 | |
189 | func (cs *clientSuite) TestConfigureWorks(c *C) { |
190 | - cli := NewPushClient(cs.configPath) |
191 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
192 | err := cli.configure() |
193 | c.Assert(err, IsNil) |
194 | c.Assert(cli.config, NotNil) |
195 | @@ -112,7 +123,7 @@ |
196 | } |
197 | |
198 | func (cs *clientSuite) TestConfigureSetsUpLog(c *C) { |
199 | - cli := NewPushClient(cs.configPath) |
200 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
201 | c.Check(cli.log, IsNil) |
202 | err := cli.configure() |
203 | c.Assert(err, IsNil) |
204 | @@ -120,7 +131,7 @@ |
205 | } |
206 | |
207 | func (cs *clientSuite) TestConfigureSetsUpPEM(c *C) { |
208 | - cli := NewPushClient(cs.configPath) |
209 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
210 | c.Check(cli.pem, IsNil) |
211 | err := cli.configure() |
212 | c.Assert(err, IsNil) |
213 | @@ -128,7 +139,7 @@ |
214 | } |
215 | |
216 | func (cs *clientSuite) TestConfigureSetsUpIdder(c *C) { |
217 | - cli := NewPushClient(cs.configPath) |
218 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
219 | c.Check(cli.idder, IsNil) |
220 | err := cli.configure() |
221 | c.Assert(err, IsNil) |
222 | @@ -136,7 +147,7 @@ |
223 | } |
224 | |
225 | func (cs *clientSuite) TestConfigureSetsUpEndpoints(c *C) { |
226 | - cli := NewPushClient(cs.configPath) |
227 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
228 | c.Check(cli.notificationsEndp, IsNil) |
229 | c.Check(cli.urlDispatcherEndp, IsNil) |
230 | c.Check(cli.connectivityEndp, IsNil) |
231 | @@ -148,7 +159,7 @@ |
232 | } |
233 | |
234 | func (cs *clientSuite) TestConfigureSetsUpConnCh(c *C) { |
235 | - cli := NewPushClient(cs.configPath) |
236 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
237 | c.Check(cli.connCh, IsNil) |
238 | err := cli.configure() |
239 | c.Assert(err, IsNil) |
240 | @@ -156,13 +167,13 @@ |
241 | } |
242 | |
243 | func (cs *clientSuite) TestConfigureBailsOnBadFilename(c *C) { |
244 | - cli := NewPushClient("/does/not/exist") |
245 | + cli := NewPushClient("/does/not/exist", cs.leveldbPath) |
246 | err := cli.configure() |
247 | c.Assert(err, NotNil) |
248 | } |
249 | |
250 | func (cs *clientSuite) TestConfigureBailsOnBadConfig(c *C) { |
251 | - cli := NewPushClient("/etc/passwd") |
252 | + cli := NewPushClient("/etc/passwd", cs.leveldbPath) |
253 | err := cli.configure() |
254 | c.Assert(err, NotNil) |
255 | } |
256 | @@ -180,7 +191,7 @@ |
257 | "recheck_timeout": "3h" |
258 | }`), 0600) |
259 | |
260 | - cli := NewPushClient(cs.configPath) |
261 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
262 | err := cli.configure() |
263 | c.Assert(err, ErrorMatches, "reading PEM file: .*") |
264 | } |
265 | @@ -198,7 +209,7 @@ |
266 | "recheck_timeout": "3h" |
267 | }`), 0600) |
268 | |
269 | - cli := NewPushClient(cs.configPath) |
270 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
271 | err := cli.configure() |
272 | c.Assert(err, ErrorMatches, "no PEM found.*") |
273 | } |
274 | @@ -208,7 +219,7 @@ |
275 | ******************************************************************/ |
276 | |
277 | func (cs *clientSuite) TestGetDeviceIdWorks(c *C) { |
278 | - cli := NewPushClient(cs.configPath) |
279 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
280 | cli.log = cs.log |
281 | cli.idder = identifier.New() |
282 | c.Check(cli.deviceId, Equals, "") |
283 | @@ -217,7 +228,7 @@ |
284 | } |
285 | |
286 | func (cs *clientSuite) TestGetDeviceIdCanFail(c *C) { |
287 | - cli := NewPushClient(cs.configPath) |
288 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
289 | cli.log = cs.log |
290 | cli.idder = idtesting.Failing() |
291 | c.Check(cli.deviceId, Equals, "") |
292 | @@ -245,7 +256,7 @@ |
293 | ) |
294 | testibus.SetWatchTicker(cEndp, make(chan bool)) |
295 | // ok, create the thing |
296 | - cli := NewPushClient(cs.configPath) |
297 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
298 | cli.log = cs.log |
299 | err := cli.configure() |
300 | c.Assert(err, IsNil) |
301 | @@ -273,7 +284,7 @@ |
302 | |
303 | // takeTheBus can, in fact, fail |
304 | func (cs *clientSuite) TestTakeTheBusCanFail(c *C) { |
305 | - cli := NewPushClient(cs.configPath) |
306 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
307 | err := cli.configure() |
308 | cli.log = cs.log |
309 | c.Assert(err, IsNil) |
310 | @@ -294,7 +305,7 @@ |
311 | ******************************************************************/ |
312 | |
313 | func (cs *clientSuite) TestHandleErr(c *C) { |
314 | - cli := NewPushClient(cs.configPath) |
315 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
316 | cli.log = cs.log |
317 | c.Assert(cli.initSession(), IsNil) |
318 | cli.hasConnectivity = true |
319 | @@ -303,11 +314,29 @@ |
320 | } |
321 | |
322 | /***************************************************************** |
323 | + levelmapFactory tests |
324 | +******************************************************************/ |
325 | + |
326 | +func (cs *clientSuite) TestLevelMapFactoryNoDbPath(c *C) { |
327 | + cli := NewPushClient(cs.configPath, "") |
328 | + ln, err := cli.levelMapFactory() |
329 | + c.Assert(err, IsNil) |
330 | + c.Check(fmt.Sprintf("%T", ln), Equals, "*levelmap.mapLevelMap") |
331 | +} |
332 | + |
333 | +func (cs *clientSuite) TestLevelMapFactoryWithDbPath(c *C) { |
334 | + cli := NewPushClient(cs.configPath, ":memory:") |
335 | + ln, err := cli.levelMapFactory() |
336 | + c.Assert(err, IsNil) |
337 | + c.Check(fmt.Sprintf("%T", ln), Equals, "*levelmap.sqliteLevelMap") |
338 | +} |
339 | + |
340 | +/***************************************************************** |
341 | handleConnState tests |
342 | ******************************************************************/ |
343 | |
344 | func (cs *clientSuite) TestHandleConnStateD2C(c *C) { |
345 | - cli := NewPushClient(cs.configPath) |
346 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
347 | cli.log = cs.log |
348 | c.Assert(cli.initSession(), IsNil) |
349 | |
350 | @@ -318,7 +347,7 @@ |
351 | } |
352 | |
353 | func (cs *clientSuite) TestHandleConnStateSame(c *C) { |
354 | - cli := NewPushClient(cs.configPath) |
355 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
356 | cli.log = cs.log |
357 | // here we want to check that we don't do anything |
358 | c.Assert(cli.session, IsNil) |
359 | @@ -332,7 +361,7 @@ |
360 | } |
361 | |
362 | func (cs *clientSuite) TestHandleConnStateC2D(c *C) { |
363 | - cli := NewPushClient(cs.configPath) |
364 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
365 | cli.log = cs.log |
366 | cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, levelmap.NewLevelMap, cs.log) |
367 | cli.session.Dial() |
368 | @@ -345,7 +374,7 @@ |
369 | } |
370 | |
371 | func (cs *clientSuite) TestHandleConnStateC2DPending(c *C) { |
372 | - cli := NewPushClient(cs.configPath) |
373 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
374 | cli.log = cs.log |
375 | cli.session, _ = session.NewSession(string(cli.config.Addr), cli.pem, cli.config.ExchangeTimeout.Duration, cli.deviceId, levelmap.NewLevelMap, cs.log) |
376 | cli.hasConnectivity = true |
377 | @@ -359,7 +388,7 @@ |
378 | ******************************************************************/ |
379 | |
380 | func (cs *clientSuite) TestHandleNotification(c *C) { |
381 | - cli := NewPushClient(cs.configPath) |
382 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
383 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), uint32(1)) |
384 | cli.notificationsEndp = endp |
385 | cli.log = cs.log |
386 | @@ -372,7 +401,7 @@ |
387 | } |
388 | |
389 | func (cs *clientSuite) TestHandleNotificationFail(c *C) { |
390 | - cli := NewPushClient(cs.configPath) |
391 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
392 | cli.log = cs.log |
393 | endp := testibus.NewTestingEndpoint(nil, condition.Work(false)) |
394 | cli.notificationsEndp = endp |
395 | @@ -384,7 +413,7 @@ |
396 | ******************************************************************/ |
397 | |
398 | func (cs *clientSuite) TestHandleClick(c *C) { |
399 | - cli := NewPushClient(cs.configPath) |
400 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
401 | cli.log = cs.log |
402 | endp := testibus.NewTestingEndpoint(nil, condition.Work(true), nil) |
403 | cli.urlDispatcherEndp = endp |
404 | @@ -401,7 +430,7 @@ |
405 | ******************************************************************/ |
406 | |
407 | func (cs *clientSuite) TestDoLoopConn(c *C) { |
408 | - cli := NewPushClient(cs.configPath) |
409 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
410 | cli.log = cs.log |
411 | cli.connCh = make(chan bool, 1) |
412 | cli.connCh <- true |
413 | @@ -413,7 +442,7 @@ |
414 | } |
415 | |
416 | func (cs *clientSuite) TestDoLoopClick(c *C) { |
417 | - cli := NewPushClient(cs.configPath) |
418 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
419 | cli.log = cs.log |
420 | c.Assert(cli.initSession(), IsNil) |
421 | aCh := make(chan notifications.RawActionReply, 1) |
422 | @@ -426,7 +455,7 @@ |
423 | } |
424 | |
425 | func (cs *clientSuite) TestDoLoopNotif(c *C) { |
426 | - cli := NewPushClient(cs.configPath) |
427 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
428 | cli.log = cs.log |
429 | c.Assert(cli.initSession(), IsNil) |
430 | cli.session.MsgCh = make(chan *session.Notification, 1) |
431 | @@ -438,7 +467,7 @@ |
432 | } |
433 | |
434 | func (cs *clientSuite) TestDoLoopErr(c *C) { |
435 | - cli := NewPushClient(cs.configPath) |
436 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
437 | cli.log = cs.log |
438 | c.Assert(cli.initSession(), IsNil) |
439 | cli.session.ErrCh = make(chan error, 1) |
440 | @@ -454,7 +483,7 @@ |
441 | ******************************************************************/ |
442 | |
443 | func (cs *clientSuite) TestDoStartWorks(c *C) { |
444 | - cli := NewPushClient(cs.configPath) |
445 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
446 | one_called := false |
447 | two_called := false |
448 | one := func() error { one_called = true; return nil } |
449 | @@ -465,7 +494,7 @@ |
450 | } |
451 | |
452 | func (cs *clientSuite) TestDoStartFailsAsExpected(c *C) { |
453 | - cli := NewPushClient(cs.configPath) |
454 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
455 | one_called := false |
456 | two_called := false |
457 | failure := errors.New("Failure") |
458 | @@ -481,7 +510,7 @@ |
459 | ******************************************************************/ |
460 | |
461 | func (cs *clientSuite) TestLoop(c *C) { |
462 | - cli := NewPushClient(cs.configPath) |
463 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
464 | cli.connCh = make(chan bool) |
465 | cli.sessionConnectedCh = make(chan uint32) |
466 | aCh := make(chan notifications.RawActionReply, 1) |
467 | @@ -559,7 +588,7 @@ |
468 | c.Skip("no dbus") |
469 | } |
470 | |
471 | - cli := NewPushClient(cs.configPath) |
472 | + cli := NewPushClient(cs.configPath, cs.leveldbPath) |
473 | // before start, everything sucks: |
474 | // no config, |
475 | c.Check(string(cli.config.Addr), Equals, "") |
476 | @@ -588,7 +617,7 @@ |
477 | } |
478 | |
479 | func (cs *clientSuite) TestStartCanFail(c *C) { |
480 | - cli := NewPushClient("/does/not/exist") |
481 | + cli := NewPushClient("/does/not/exist", cs.leveldbPath) |
482 | // easiest way for it to fail is to feed it a bad config |
483 | err := cli.Start() |
484 | // and it works. Err. Doesn't. |
good time to do: pushClient => PushClient ?
c.Check( fmt.Sprintf( "%T", ln), Equals, "*levelmap. sqliteLevelMap" )
maybe giving LevelMap again a String() ?