Merge lp:~rogpeppe/gozk/better-error-messages into lp:gozk/zookeeper
- better-error-messages
- Merge into zookeeper
Status: | Merged |
---|---|
Merged at revision: | 33 |
Proposed branch: | lp:~rogpeppe/gozk/better-error-messages |
Merge into: | lp:gozk/zookeeper |
Diff against target: |
638 lines (+162/-91) 4 files modified
close_test.go (+1/-1) retry_test.go (+3/-3) zk.go (+104/-72) zk_test.go (+54/-15) |
To merge this branch: | bzr merge lp:~rogpeppe/gozk/better-error-messages |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
The Go Language Gophers | Pending | ||
Review via email: mp+97613@code.launchpad.net |
Commit message
Description of the change
zookeeper: make error messages more informative.
Roger Peppe (rogpeppe) wrote : | # |
Gustavo Niemeyer (niemeyer) wrote : | # |
Very nice. Just a few ideas.
https:/
File zk.go (right):
https:/
zk.go:120: // HasErrorCode returns whether the error has the given
It's nice to have this. I was a bit concerned it might be boring to deal
with it.
Can we please call the function as IsError? For example:
if zookeeper.
Also, for the comment, I suggest something like this:
// IsError returns whether err is an *Error with the given
// error code.
https:/
zk.go:457: func errClosing(op string) error {
Can we please call this closingError and put it next to zkError? They're
pretty related.
Also, closingError should take a path, as many of the places it's used
have it in context.
https:/
zk.go:584: return nil, nil, nil, errClosing(
The upper/lower casing is inconsistent. We have "ACL", "addauth",
"setACL", and "childrenW". I suggest we lowercase them all completely.
This is an error message, and more of a hint than something that has to
reflect the method name used exactly.
https:/
zk.go:925: if err != nil && !HasErrorCode(err, ZNONODE) {
err != nil isn't necessary here.
https:/
File zk_test.go (right):
https:/
zk_test.go:1: package zookeeper_test
The Error.Error method has a few different possibilities now as far as
building the error message goes. Would you mind to add a test case for
those? We don't want to find out that we broke that logic at some point
in actual usage.
https:/
zk_test.go:161: c.Assert(err, ErrorMatches, `zookeeper: get
"/non-existent": no node`)
Nice!
Roger Peppe (rogpeppe) wrote : | # |
Please take a look.
- 39. By Roger Peppe
-
changes for review
- 40. By Roger Peppe
-
go fmt
Roger Peppe (rogpeppe) wrote : | # |
https:/
File zk.go (right):
https:/
zk.go:120: // HasErrorCode returns whether the error has the given
On 2012/03/15 14:31:38, niemeyer wrote:
> It's nice to have this. I was a bit concerned it might be boring to
deal with
> it.
> Can we please call the function as IsError? For example:
> if zookeeper.
> Also, for the comment, I suggest something like this:
> // IsError returns whether err is an *Error with the given
> // error code.
yeah, that's nicer. done.
https:/
zk.go:457: func errClosing(op string) error {
On 2012/03/15 14:31:38, niemeyer wrote:
> Can we please call this closingError and put it next to zkError?
They're pretty
> related.
> Also, closingError should take a path, as many of the places it's used
have it
> in context.
Done.
https:/
zk.go:584: return nil, nil, nil, errClosing(
On 2012/03/15 14:31:38, niemeyer wrote:
> The upper/lower casing is inconsistent. We have "ACL", "addauth",
"setACL", and
> "childrenW". I suggest we lowercase them all completely. This is an
error
> message, and more of a hint than something that has to reflect the
method name
> used exactly.
yeah, i wondered about that. done.
https:/
zk.go:925: if err != nil && !HasErrorCode(err, ZNONODE) {
On 2012/03/15 14:31:38, niemeyer wrote:
> err != nil isn't necessary here.
it is necessary, i think, otherwise we'll return when err is nil.
https:/
File zk_test.go (right):
https:/
zk_test.go:1: package zookeeper_test
On 2012/03/15 14:31:38, niemeyer wrote:
> The Error.Error method has a few different possibilities now as far as
building
> the error message goes. Would you mind to add a test case for those?
We don't
> want to find out that we broke that logic at some point in actual
usage.
Done.
Gustavo Niemeyer (niemeyer) wrote : | # |
Gustavo Niemeyer (niemeyer) wrote : | # |
Roger Peppe (rogpeppe) wrote : | # |
*** Submitted:
zookeeper: make error messages more informative.
R=niemeyer
CC=
https:/
Preview Diff
1 | === modified file 'close_test.go' | |||
2 | --- close_test.go 2012-02-28 09:13:50 +0000 | |||
3 | +++ close_test.go 2012-03-15 15:14:18 +0000 | |||
4 | @@ -130,7 +130,7 @@ | |||
5 | 130 | } | 130 | } |
6 | 131 | p.close() | 131 | p.close() |
7 | 132 | err = f(conn, "/closetest") | 132 | err = f(conn, "/closetest") |
9 | 133 | c.Check(err, Equals, zk.ZCLOSING) | 133 | c.Check(zk.IsError(err, zk.ZCLOSING), Equals, true, Commentf("%v", err)) |
10 | 134 | } | 134 | } |
11 | 135 | } | 135 | } |
12 | 136 | 136 | ||
13 | 137 | 137 | ||
14 | === modified file 'retry_test.go' | |||
15 | --- retry_test.go 2012-02-27 17:15:50 +0000 | |||
16 | +++ retry_test.go 2012-03-15 15:14:18 +0000 | |||
17 | @@ -209,7 +209,7 @@ | |||
18 | 209 | return "", nil | 209 | return "", nil |
19 | 210 | }) | 210 | }) |
20 | 211 | c.Assert(err, NotNil) | 211 | c.Assert(err, NotNil) |
22 | 212 | c.Assert(err, Equals, zk.ZNOAUTH) | 212 | c.Check(zk.IsError(err, zk.ZNOAUTH), Equals, true, Commentf("%v", err)) |
23 | 213 | 213 | ||
24 | 214 | stat, err := conn.Exists("/test") | 214 | stat, err := conn.Exists("/test") |
25 | 215 | c.Assert(err, IsNil) | 215 | c.Assert(err, IsNil) |
26 | @@ -232,7 +232,7 @@ | |||
27 | 232 | called = true | 232 | called = true |
28 | 233 | return "", nil | 233 | return "", nil |
29 | 234 | }) | 234 | }) |
31 | 235 | c.Assert(err, Equals, zk.ZNOAUTH) | 235 | c.Check(zk.IsError(err, zk.ZNOAUTH), Equals, true, Commentf("%v", err)) |
32 | 236 | 236 | ||
33 | 237 | stat, err := conn.Exists("/test") | 237 | stat, err := conn.Exists("/test") |
34 | 238 | c.Assert(err, IsNil) | 238 | c.Assert(err, IsNil) |
35 | @@ -256,7 +256,7 @@ | |||
36 | 256 | return "", nil | 256 | return "", nil |
37 | 257 | }) | 257 | }) |
38 | 258 | c.Assert(err, NotNil) | 258 | c.Assert(err, NotNil) |
40 | 259 | c.Assert(err, Equals, zk.ZNOAUTH) | 259 | c.Check(zk.IsError(err, zk.ZNOAUTH), Equals, true, Commentf("%v", err)) |
41 | 260 | 260 | ||
42 | 261 | stat, err := conn.Exists("/test/sub") | 261 | stat, err := conn.Exists("/test/sub") |
43 | 262 | c.Assert(err, IsNil) | 262 | c.Assert(err, IsNil) |
44 | 263 | 263 | ||
45 | === modified file 'zk.go' | |||
46 | --- zk.go 2012-02-27 17:15:50 +0000 | |||
47 | +++ zk.go 2012-03-15 15:14:18 +0000 | |||
48 | @@ -98,57 +98,89 @@ | |||
49 | 98 | } | 98 | } |
50 | 99 | 99 | ||
51 | 100 | // Error represents a ZooKeeper error. | 100 | // Error represents a ZooKeeper error. |
53 | 101 | type Error int | 101 | type Error struct { |
54 | 102 | Op string | ||
55 | 103 | Code ErrorCode | ||
56 | 104 | // SystemError holds an error if Code is ZSYSTEMERROR. | ||
57 | 105 | SystemError error | ||
58 | 106 | Path string | ||
59 | 107 | } | ||
60 | 108 | |||
61 | 109 | func (e *Error) Error() string { | ||
62 | 110 | s := e.Code.String() | ||
63 | 111 | if e.Code == ZSYSTEMERROR && e.SystemError != nil { | ||
64 | 112 | s = e.SystemError.Error() | ||
65 | 113 | } | ||
66 | 114 | if e.Path == "" { | ||
67 | 115 | return fmt.Sprintf("zookeeper: %s: %v", e.Op, s) | ||
68 | 116 | } | ||
69 | 117 | return fmt.Sprintf("zookeeper: %s %q: %v", e.Op, e.Path, s) | ||
70 | 118 | } | ||
71 | 119 | |||
72 | 120 | // IsError returns whether the error is a *Error | ||
73 | 121 | // with the given error code. | ||
74 | 122 | func IsError(err error, code ErrorCode) bool { | ||
75 | 123 | if err, _ := err.(*Error); err != nil { | ||
76 | 124 | return err.Code == code | ||
77 | 125 | } | ||
78 | 126 | return false | ||
79 | 127 | } | ||
80 | 128 | |||
81 | 129 | // ErrorCode represents a kind of ZooKeeper error. | ||
82 | 130 | type ErrorCode int | ||
83 | 102 | 131 | ||
84 | 103 | const ( | 132 | const ( |
109 | 104 | ZOK Error = C.ZOK | 133 | ZOK ErrorCode = C.ZOK |
110 | 105 | ZSYSTEMERROR Error = C.ZSYSTEMERROR | 134 | ZSYSTEMERROR ErrorCode = C.ZSYSTEMERROR |
111 | 106 | ZRUNTIMEINCONSISTENCY Error = C.ZRUNTIMEINCONSISTENCY | 135 | ZRUNTIMEINCONSISTENCY ErrorCode = C.ZRUNTIMEINCONSISTENCY |
112 | 107 | ZDATAINCONSISTENCY Error = C.ZDATAINCONSISTENCY | 136 | ZDATAINCONSISTENCY ErrorCode = C.ZDATAINCONSISTENCY |
113 | 108 | ZCONNECTIONLOSS Error = C.ZCONNECTIONLOSS | 137 | ZCONNECTIONLOSS ErrorCode = C.ZCONNECTIONLOSS |
114 | 109 | ZMARSHALLINGERROR Error = C.ZMARSHALLINGERROR | 138 | ZMARSHALLINGERROR ErrorCode = C.ZMARSHALLINGERROR |
115 | 110 | ZUNIMPLEMENTED Error = C.ZUNIMPLEMENTED | 139 | ZUNIMPLEMENTED ErrorCode = C.ZUNIMPLEMENTED |
116 | 111 | ZOPERATIONTIMEOUT Error = C.ZOPERATIONTIMEOUT | 140 | ZOPERATIONTIMEOUT ErrorCode = C.ZOPERATIONTIMEOUT |
117 | 112 | ZBADARGUMENTS Error = C.ZBADARGUMENTS | 141 | ZBADARGUMENTS ErrorCode = C.ZBADARGUMENTS |
118 | 113 | ZINVALIDSTATE Error = C.ZINVALIDSTATE | 142 | ZINVALIDSTATE ErrorCode = C.ZINVALIDSTATE |
119 | 114 | ZAPIERROR Error = C.ZAPIERROR | 143 | ZAPIERROR ErrorCode = C.ZAPIERROR |
120 | 115 | ZNONODE Error = C.ZNONODE | 144 | ZNONODE ErrorCode = C.ZNONODE |
121 | 116 | ZNOAUTH Error = C.ZNOAUTH | 145 | ZNOAUTH ErrorCode = C.ZNOAUTH |
122 | 117 | ZBADVERSION Error = C.ZBADVERSION | 146 | ZBADVERSION ErrorCode = C.ZBADVERSION |
123 | 118 | ZNOCHILDRENFOREPHEMERALS Error = C.ZNOCHILDRENFOREPHEMERALS | 147 | ZNOCHILDRENFOREPHEMERALS ErrorCode = C.ZNOCHILDRENFOREPHEMERALS |
124 | 119 | ZNODEEXISTS Error = C.ZNODEEXISTS | 148 | ZNODEEXISTS ErrorCode = C.ZNODEEXISTS |
125 | 120 | ZNOTEMPTY Error = C.ZNOTEMPTY | 149 | ZNOTEMPTY ErrorCode = C.ZNOTEMPTY |
126 | 121 | ZSESSIONEXPIRED Error = C.ZSESSIONEXPIRED | 150 | ZSESSIONEXPIRED ErrorCode = C.ZSESSIONEXPIRED |
127 | 122 | ZINVALIDCALLBACK Error = C.ZINVALIDCALLBACK | 151 | ZINVALIDCALLBACK ErrorCode = C.ZINVALIDCALLBACK |
128 | 123 | ZINVALIDACL Error = C.ZINVALIDACL | 152 | ZINVALIDACL ErrorCode = C.ZINVALIDACL |
129 | 124 | ZAUTHFAILED Error = C.ZAUTHFAILED | 153 | ZAUTHFAILED ErrorCode = C.ZAUTHFAILED |
130 | 125 | ZCLOSING Error = C.ZCLOSING | 154 | ZCLOSING ErrorCode = C.ZCLOSING |
131 | 126 | ZNOTHING Error = C.ZNOTHING | 155 | ZNOTHING ErrorCode = C.ZNOTHING |
132 | 127 | ZSESSIONMOVED Error = C.ZSESSIONMOVED | 156 | ZSESSIONMOVED ErrorCode = C.ZSESSIONMOVED |
133 | 128 | ) | 157 | ) |
134 | 129 | 158 | ||
137 | 130 | func (error Error) Error() string { | 159 | func (code ErrorCode) String() string { |
138 | 131 | return C.GoString(C.zerror(C.int(error))) // Static, no need to free it. | 160 | return C.GoString(C.zerror(C.int(code))) // Static, no need to free it. |
139 | 132 | } | 161 | } |
140 | 133 | 162 | ||
141 | 134 | // zkError creates an appropriate error return from | 163 | // zkError creates an appropriate error return from |
142 | 135 | // a ZooKeeper status and the errno return from a C API | 164 | // a ZooKeeper status and the errno return from a C API |
143 | 136 | // call. | 165 | // call. |
148 | 137 | func zkError(rc C.int, cerr error) error { | 166 | func zkError(rc C.int, cerr error, op, path string) error { |
149 | 138 | code := Error(rc) | 167 | code := ErrorCode(rc) |
150 | 139 | switch code { | 168 | if code == ZOK { |
147 | 140 | case ZOK: | ||
151 | 141 | return nil | 169 | return nil |
152 | 170 | } | ||
153 | 171 | err := &Error{ | ||
154 | 172 | Op: op, | ||
155 | 173 | Code: code, | ||
156 | 174 | Path: path, | ||
157 | 175 | } | ||
158 | 176 | if code == ZSYSTEMERROR { | ||
159 | 177 | err.SystemError = cerr | ||
160 | 178 | } | ||
161 | 179 | return err | ||
162 | 180 | } | ||
163 | 142 | 181 | ||
173 | 143 | case ZSYSTEMERROR: | 182 | func closingError(op, path string) error { |
174 | 144 | // If a ZooKeeper call returns ZSYSTEMERROR, then | 183 | return zkError(C.int(ZCLOSING), nil, op, path) |
166 | 145 | // errno becomes significant. If errno has not been | ||
167 | 146 | // set, then we will return ZSYSTEMERROR nonetheless. | ||
168 | 147 | if cerr != nil { | ||
169 | 148 | return cerr | ||
170 | 149 | } | ||
171 | 150 | } | ||
172 | 151 | return code | ||
175 | 152 | } | 184 | } |
176 | 153 | 185 | ||
177 | 154 | // Constants for SetLogLevel. | 186 | // Constants for SetLogLevel. |
178 | @@ -419,7 +451,7 @@ | |||
179 | 419 | C.free(unsafe.Pointer(cservers)) | 451 | C.free(unsafe.Pointer(cservers)) |
180 | 420 | if handle == nil { | 452 | if handle == nil { |
181 | 421 | conn.closeAllWatches() | 453 | conn.closeAllWatches() |
183 | 422 | return nil, nil, zkError(C.int(ZSYSTEMERROR), cerr) | 454 | return nil, nil, zkError(C.int(ZSYSTEMERROR), cerr, "dial", "") |
184 | 423 | } | 455 | } |
185 | 424 | conn.handle = handle | 456 | conn.handle = handle |
186 | 425 | runWatchLoop() | 457 | runWatchLoop() |
187 | @@ -444,7 +476,7 @@ | |||
188 | 444 | if conn.handle == nil { | 476 | if conn.handle == nil { |
189 | 445 | // ZooKeeper may hang indefinitely if a handler is closed twice, | 477 | // ZooKeeper may hang indefinitely if a handler is closed twice, |
190 | 446 | // so we get in the way and prevent it from happening. | 478 | // so we get in the way and prevent it from happening. |
192 | 447 | return ZCLOSING | 479 | return closingError("close", "") |
193 | 448 | } | 480 | } |
194 | 449 | rc, cerr := C.zookeeper_close(conn.handle) | 481 | rc, cerr := C.zookeeper_close(conn.handle) |
195 | 450 | 482 | ||
196 | @@ -454,7 +486,7 @@ | |||
197 | 454 | // At this point, nothing else should need conn.handle. | 486 | // At this point, nothing else should need conn.handle. |
198 | 455 | conn.handle = nil | 487 | conn.handle = nil |
199 | 456 | 488 | ||
201 | 457 | return zkError(rc, cerr) | 489 | return zkError(rc, cerr, "close", "") |
202 | 458 | } | 490 | } |
203 | 459 | 491 | ||
204 | 460 | // Get returns the data and status from an existing node. err will be nil, | 492 | // Get returns the data and status from an existing node. err will be nil, |
205 | @@ -464,7 +496,7 @@ | |||
206 | 464 | conn.mutex.RLock() | 496 | conn.mutex.RLock() |
207 | 465 | defer conn.mutex.RUnlock() | 497 | defer conn.mutex.RUnlock() |
208 | 466 | if conn.handle == nil { | 498 | if conn.handle == nil { |
210 | 467 | return "", nil, ZCLOSING | 499 | return "", nil, closingError("get", path) |
211 | 468 | } | 500 | } |
212 | 469 | 501 | ||
213 | 470 | cpath := C.CString(path) | 502 | cpath := C.CString(path) |
214 | @@ -476,7 +508,7 @@ | |||
215 | 476 | var cstat Stat | 508 | var cstat Stat |
216 | 477 | rc, cerr := C.zoo_wget(conn.handle, cpath, nil, nil, cbuffer, &cbufferLen, &cstat.c) | 509 | rc, cerr := C.zoo_wget(conn.handle, cpath, nil, nil, cbuffer, &cbufferLen, &cstat.c) |
217 | 478 | if rc != C.ZOK { | 510 | if rc != C.ZOK { |
219 | 479 | return "", nil, zkError(rc, cerr) | 511 | return "", nil, zkError(rc, cerr, "get", path) |
220 | 480 | } | 512 | } |
221 | 481 | 513 | ||
222 | 482 | result := C.GoStringN(cbuffer, cbufferLen) | 514 | result := C.GoStringN(cbuffer, cbufferLen) |
223 | @@ -491,7 +523,7 @@ | |||
224 | 491 | conn.mutex.RLock() | 523 | conn.mutex.RLock() |
225 | 492 | defer conn.mutex.RUnlock() | 524 | defer conn.mutex.RUnlock() |
226 | 493 | if conn.handle == nil { | 525 | if conn.handle == nil { |
228 | 494 | return "", nil, nil, ZCLOSING | 526 | return "", nil, nil, closingError("getw", path) |
229 | 495 | } | 527 | } |
230 | 496 | 528 | ||
231 | 497 | cpath := C.CString(path) | 529 | cpath := C.CString(path) |
232 | @@ -506,7 +538,7 @@ | |||
233 | 506 | rc, cerr := C.zoo_wget(conn.handle, cpath, C.watch_handler, unsafe.Pointer(watchId), cbuffer, &cbufferLen, &cstat.c) | 538 | rc, cerr := C.zoo_wget(conn.handle, cpath, C.watch_handler, unsafe.Pointer(watchId), cbuffer, &cbufferLen, &cstat.c) |
234 | 507 | if rc != C.ZOK { | 539 | if rc != C.ZOK { |
235 | 508 | conn.forgetWatch(watchId) | 540 | conn.forgetWatch(watchId) |
237 | 509 | return "", nil, nil, zkError(rc, cerr) | 541 | return "", nil, nil, zkError(rc, cerr, "getw", path) |
238 | 510 | } | 542 | } |
239 | 511 | 543 | ||
240 | 512 | result := C.GoStringN(cbuffer, cbufferLen) | 544 | result := C.GoStringN(cbuffer, cbufferLen) |
241 | @@ -519,7 +551,7 @@ | |||
242 | 519 | conn.mutex.RLock() | 551 | conn.mutex.RLock() |
243 | 520 | defer conn.mutex.RUnlock() | 552 | defer conn.mutex.RUnlock() |
244 | 521 | if conn.handle == nil { | 553 | if conn.handle == nil { |
246 | 522 | return nil, nil, ZCLOSING | 554 | return nil, nil, closingError("children", path) |
247 | 523 | } | 555 | } |
248 | 524 | 556 | ||
249 | 525 | cpath := C.CString(path) | 557 | cpath := C.CString(path) |
250 | @@ -536,7 +568,7 @@ | |||
251 | 536 | if rc == C.ZOK { | 568 | if rc == C.ZOK { |
252 | 537 | stat = &cstat | 569 | stat = &cstat |
253 | 538 | } else { | 570 | } else { |
255 | 539 | err = zkError(rc, cerr) | 571 | err = zkError(rc, cerr, "children", path) |
256 | 540 | } | 572 | } |
257 | 541 | return | 573 | return |
258 | 542 | } | 574 | } |
259 | @@ -549,7 +581,7 @@ | |||
260 | 549 | conn.mutex.RLock() | 581 | conn.mutex.RLock() |
261 | 550 | defer conn.mutex.RUnlock() | 582 | defer conn.mutex.RUnlock() |
262 | 551 | if conn.handle == nil { | 583 | if conn.handle == nil { |
264 | 552 | return nil, nil, nil, ZCLOSING | 584 | return nil, nil, nil, closingError("childrenw", path) |
265 | 553 | } | 585 | } |
266 | 554 | 586 | ||
267 | 555 | cpath := C.CString(path) | 587 | cpath := C.CString(path) |
268 | @@ -570,7 +602,7 @@ | |||
269 | 570 | watch = watchChannel | 602 | watch = watchChannel |
270 | 571 | } else { | 603 | } else { |
271 | 572 | conn.forgetWatch(watchId) | 604 | conn.forgetWatch(watchId) |
273 | 573 | err = zkError(rc, cerr) | 605 | err = zkError(rc, cerr, "childrenw", path) |
274 | 574 | } | 606 | } |
275 | 575 | return | 607 | return |
276 | 576 | } | 608 | } |
277 | @@ -595,7 +627,7 @@ | |||
278 | 595 | conn.mutex.RLock() | 627 | conn.mutex.RLock() |
279 | 596 | defer conn.mutex.RUnlock() | 628 | defer conn.mutex.RUnlock() |
280 | 597 | if conn.handle == nil { | 629 | if conn.handle == nil { |
282 | 598 | return nil, ZCLOSING | 630 | return nil, closingError("exists", path) |
283 | 599 | } | 631 | } |
284 | 600 | 632 | ||
285 | 601 | cpath := C.CString(path) | 633 | cpath := C.CString(path) |
286 | @@ -610,7 +642,7 @@ | |||
287 | 610 | if rc == C.ZOK { | 642 | if rc == C.ZOK { |
288 | 611 | stat = &cstat | 643 | stat = &cstat |
289 | 612 | } else if rc != C.ZNONODE { | 644 | } else if rc != C.ZNONODE { |
291 | 613 | err = zkError(rc, cerr) | 645 | err = zkError(rc, cerr, "exists", path) |
292 | 614 | } | 646 | } |
293 | 615 | return | 647 | return |
294 | 616 | } | 648 | } |
295 | @@ -624,7 +656,7 @@ | |||
296 | 624 | conn.mutex.RLock() | 656 | conn.mutex.RLock() |
297 | 625 | defer conn.mutex.RUnlock() | 657 | defer conn.mutex.RUnlock() |
298 | 626 | if conn.handle == nil { | 658 | if conn.handle == nil { |
300 | 627 | return nil, nil, ZCLOSING | 659 | return nil, nil, closingError("existsw", path) |
301 | 628 | } | 660 | } |
302 | 629 | 661 | ||
303 | 630 | cpath := C.CString(path) | 662 | cpath := C.CString(path) |
304 | @@ -638,7 +670,7 @@ | |||
305 | 638 | // We diverge a bit from the usual here: a ZNONODE is not an error | 670 | // We diverge a bit from the usual here: a ZNONODE is not an error |
306 | 639 | // for an exists call, otherwise every Exists call would have to check | 671 | // for an exists call, otherwise every Exists call would have to check |
307 | 640 | // for err != nil and err.Code() != ZNONODE. | 672 | // for err != nil and err.Code() != ZNONODE. |
309 | 641 | switch Error(rc) { | 673 | switch ErrorCode(rc) { |
310 | 642 | case ZOK: | 674 | case ZOK: |
311 | 643 | stat = &cstat | 675 | stat = &cstat |
312 | 644 | watch = watchChannel | 676 | watch = watchChannel |
313 | @@ -646,7 +678,7 @@ | |||
314 | 646 | watch = watchChannel | 678 | watch = watchChannel |
315 | 647 | default: | 679 | default: |
316 | 648 | conn.forgetWatch(watchId) | 680 | conn.forgetWatch(watchId) |
318 | 649 | err = zkError(rc, cerr) | 681 | err = zkError(rc, cerr, "existsw", path) |
319 | 650 | } | 682 | } |
320 | 651 | return | 683 | return |
321 | 652 | } | 684 | } |
322 | @@ -664,7 +696,7 @@ | |||
323 | 664 | conn.mutex.RLock() | 696 | conn.mutex.RLock() |
324 | 665 | defer conn.mutex.RUnlock() | 697 | defer conn.mutex.RUnlock() |
325 | 666 | if conn.handle == nil { | 698 | if conn.handle == nil { |
327 | 667 | return "", ZCLOSING | 699 | return "", closingError("close", path) |
328 | 668 | } | 700 | } |
329 | 669 | 701 | ||
330 | 670 | cpath := C.CString(path) | 702 | cpath := C.CString(path) |
331 | @@ -684,7 +716,7 @@ | |||
332 | 684 | if rc == C.ZOK { | 716 | if rc == C.ZOK { |
333 | 685 | pathCreated = C.GoString(cpathCreated) | 717 | pathCreated = C.GoString(cpathCreated) |
334 | 686 | } else { | 718 | } else { |
336 | 687 | err = zkError(rc, cerr) | 719 | err = zkError(rc, cerr, "create", path) |
337 | 688 | } | 720 | } |
338 | 689 | return | 721 | return |
339 | 690 | } | 722 | } |
340 | @@ -701,7 +733,7 @@ | |||
341 | 701 | conn.mutex.RLock() | 733 | conn.mutex.RLock() |
342 | 702 | defer conn.mutex.RUnlock() | 734 | defer conn.mutex.RUnlock() |
343 | 703 | if conn.handle == nil { | 735 | if conn.handle == nil { |
345 | 704 | return nil, ZCLOSING | 736 | return nil, closingError("set", path) |
346 | 705 | } | 737 | } |
347 | 706 | 738 | ||
348 | 707 | cpath := C.CString(path) | 739 | cpath := C.CString(path) |
349 | @@ -714,7 +746,7 @@ | |||
350 | 714 | if rc == C.ZOK { | 746 | if rc == C.ZOK { |
351 | 715 | stat = &cstat | 747 | stat = &cstat |
352 | 716 | } else { | 748 | } else { |
354 | 717 | err = zkError(rc, cerr) | 749 | err = zkError(rc, cerr, "set", path) |
355 | 718 | } | 750 | } |
356 | 719 | return | 751 | return |
357 | 720 | } | 752 | } |
358 | @@ -726,13 +758,13 @@ | |||
359 | 726 | conn.mutex.RLock() | 758 | conn.mutex.RLock() |
360 | 727 | defer conn.mutex.RUnlock() | 759 | defer conn.mutex.RUnlock() |
361 | 728 | if conn.handle == nil { | 760 | if conn.handle == nil { |
363 | 729 | return ZCLOSING | 761 | return closingError("delete", path) |
364 | 730 | } | 762 | } |
365 | 731 | 763 | ||
366 | 732 | cpath := C.CString(path) | 764 | cpath := C.CString(path) |
367 | 733 | defer C.free(unsafe.Pointer(cpath)) | 765 | defer C.free(unsafe.Pointer(cpath)) |
368 | 734 | rc, cerr := C.zoo_delete(conn.handle, cpath, C.int(version)) | 766 | rc, cerr := C.zoo_delete(conn.handle, cpath, C.int(version)) |
370 | 735 | return zkError(rc, cerr) | 767 | return zkError(rc, cerr, "delete", path) |
371 | 736 | } | 768 | } |
372 | 737 | 769 | ||
373 | 738 | // AddAuth adds a new authentication certificate to the ZooKeeper | 770 | // AddAuth adds a new authentication certificate to the ZooKeeper |
374 | @@ -744,7 +776,7 @@ | |||
375 | 744 | conn.mutex.RLock() | 776 | conn.mutex.RLock() |
376 | 745 | defer conn.mutex.RUnlock() | 777 | defer conn.mutex.RUnlock() |
377 | 746 | if conn.handle == nil { | 778 | if conn.handle == nil { |
379 | 747 | return ZCLOSING | 779 | return closingError("addauth", "") |
380 | 748 | } | 780 | } |
381 | 749 | 781 | ||
382 | 750 | cscheme := C.CString(scheme) | 782 | cscheme := C.CString(scheme) |
383 | @@ -760,13 +792,13 @@ | |||
384 | 760 | 792 | ||
385 | 761 | rc, cerr := C.zoo_add_auth(conn.handle, cscheme, ccert, C.int(len(cert)), C.handle_void_completion, unsafe.Pointer(data)) | 793 | rc, cerr := C.zoo_add_auth(conn.handle, cscheme, ccert, C.int(len(cert)), C.handle_void_completion, unsafe.Pointer(data)) |
386 | 762 | if rc != C.ZOK { | 794 | if rc != C.ZOK { |
388 | 763 | return zkError(rc, cerr) | 795 | return zkError(rc, cerr, "addauth", "") |
389 | 764 | } | 796 | } |
390 | 765 | 797 | ||
391 | 766 | C.wait_for_completion(data) | 798 | C.wait_for_completion(data) |
392 | 767 | 799 | ||
393 | 768 | rc = C.int(uintptr(data.data)) | 800 | rc = C.int(uintptr(data.data)) |
395 | 769 | return zkError(rc, nil) | 801 | return zkError(rc, nil, "addauth", "") |
396 | 770 | } | 802 | } |
397 | 771 | 803 | ||
398 | 772 | // ACL returns the access control list for path. | 804 | // ACL returns the access control list for path. |
399 | @@ -774,7 +806,7 @@ | |||
400 | 774 | conn.mutex.RLock() | 806 | conn.mutex.RLock() |
401 | 775 | defer conn.mutex.RUnlock() | 807 | defer conn.mutex.RUnlock() |
402 | 776 | if conn.handle == nil { | 808 | if conn.handle == nil { |
404 | 777 | return nil, nil, ZCLOSING | 809 | return nil, nil, closingError("acl", path) |
405 | 778 | } | 810 | } |
406 | 779 | 811 | ||
407 | 780 | cpath := C.CString(path) | 812 | cpath := C.CString(path) |
408 | @@ -785,7 +817,7 @@ | |||
409 | 785 | var cstat Stat | 817 | var cstat Stat |
410 | 786 | rc, cerr := C.zoo_get_acl(conn.handle, cpath, &caclv, &cstat.c) | 818 | rc, cerr := C.zoo_get_acl(conn.handle, cpath, &caclv, &cstat.c) |
411 | 787 | if rc != C.ZOK { | 819 | if rc != C.ZOK { |
413 | 788 | return nil, nil, zkError(rc, cerr) | 820 | return nil, nil, zkError(rc, cerr, "acl", path) |
414 | 789 | } | 821 | } |
415 | 790 | 822 | ||
416 | 791 | aclv := parseACLVector(&caclv) | 823 | aclv := parseACLVector(&caclv) |
417 | @@ -798,7 +830,7 @@ | |||
418 | 798 | conn.mutex.RLock() | 830 | conn.mutex.RLock() |
419 | 799 | defer conn.mutex.RUnlock() | 831 | defer conn.mutex.RUnlock() |
420 | 800 | if conn.handle == nil { | 832 | if conn.handle == nil { |
422 | 801 | return ZCLOSING | 833 | return closingError("setacl", path) |
423 | 802 | } | 834 | } |
424 | 803 | 835 | ||
425 | 804 | cpath := C.CString(path) | 836 | cpath := C.CString(path) |
426 | @@ -808,7 +840,7 @@ | |||
427 | 808 | defer C.deallocate_ACL_vector(caclv) | 840 | defer C.deallocate_ACL_vector(caclv) |
428 | 809 | 841 | ||
429 | 810 | rc, cerr := C.zoo_set_acl(conn.handle, cpath, C.int(version), caclv) | 842 | rc, cerr := C.zoo_set_acl(conn.handle, cpath, C.int(version), caclv) |
431 | 811 | return zkError(rc, cerr) | 843 | return zkError(rc, cerr, "setacl", path) |
432 | 812 | } | 844 | } |
433 | 813 | 845 | ||
434 | 814 | func parseACLVector(caclv *C.struct_ACL_vector) []ACL { | 846 | func parseACLVector(caclv *C.struct_ACL_vector) []ACL { |
435 | @@ -890,7 +922,7 @@ | |||
436 | 890 | func (conn *Conn) RetryChange(path string, flags int, acl []ACL, changeFunc ChangeFunc) error { | 922 | func (conn *Conn) RetryChange(path string, flags int, acl []ACL, changeFunc ChangeFunc) error { |
437 | 891 | for { | 923 | for { |
438 | 892 | oldValue, oldStat, err := conn.Get(path) | 924 | oldValue, oldStat, err := conn.Get(path) |
440 | 893 | if err != nil && err != ZNONODE { | 925 | if err != nil && !IsError(err, ZNONODE) { |
441 | 894 | return err | 926 | return err |
442 | 895 | } | 927 | } |
443 | 896 | newValue, err := changeFunc(oldValue, oldStat) | 928 | newValue, err := changeFunc(oldValue, oldStat) |
444 | @@ -899,7 +931,7 @@ | |||
445 | 899 | } | 931 | } |
446 | 900 | if oldStat == nil { | 932 | if oldStat == nil { |
447 | 901 | _, err := conn.Create(path, newValue, flags, acl) | 933 | _, err := conn.Create(path, newValue, flags, acl) |
449 | 902 | if err == nil || err != ZNODEEXISTS { | 934 | if err == nil || !IsError(err, ZNODEEXISTS) { |
450 | 903 | return err | 935 | return err |
451 | 904 | } | 936 | } |
452 | 905 | continue | 937 | continue |
453 | @@ -908,7 +940,7 @@ | |||
454 | 908 | return nil // Nothing to do. | 940 | return nil // Nothing to do. |
455 | 909 | } | 941 | } |
456 | 910 | _, err = conn.Set(path, newValue, oldStat.Version()) | 942 | _, err = conn.Set(path, newValue, oldStat.Version()) |
458 | 911 | if err == nil || (err != ZBADVERSION && err != ZNONODE) { | 943 | if err == nil || !IsError(err, ZBADVERSION) && !IsError(err, ZNONODE) { |
459 | 912 | return err | 944 | return err |
460 | 913 | } | 945 | } |
461 | 914 | } | 946 | } |
462 | 915 | 947 | ||
463 | === modified file 'zk_test.go' | |||
464 | --- zk_test.go 2012-02-27 17:15:50 +0000 | |||
465 | +++ zk_test.go 2012-03-15 15:14:18 +0000 | |||
466 | @@ -1,6 +1,7 @@ | |||
467 | 1 | package zookeeper_test | 1 | package zookeeper_test |
468 | 2 | 2 | ||
469 | 3 | import ( | 3 | import ( |
470 | 4 | "errors" | ||
471 | 4 | . "launchpad.net/gocheck" | 5 | . "launchpad.net/gocheck" |
472 | 5 | zk "launchpad.net/gozk/zookeeper" | 6 | zk "launchpad.net/gozk/zookeeper" |
473 | 6 | "time" | 7 | "time" |
474 | @@ -25,7 +26,45 @@ | |||
475 | 25 | } | 26 | } |
476 | 26 | c.Assert(conn, IsNil) | 27 | c.Assert(conn, IsNil) |
477 | 27 | c.Assert(watch, IsNil) | 28 | c.Assert(watch, IsNil) |
479 | 28 | c.Assert(err, ErrorMatches, "invalid argument") | 29 | c.Assert(err, ErrorMatches, "zookeeper: dial: invalid argument") |
480 | 30 | } | ||
481 | 31 | |||
482 | 32 | func (s *S) TestErrorMessages(c *C) { | ||
483 | 33 | tests := []struct { | ||
484 | 34 | err zk.Error | ||
485 | 35 | msg string | ||
486 | 36 | }{{ | ||
487 | 37 | zk.Error{ | ||
488 | 38 | Op: "foo", | ||
489 | 39 | Code: zk.ZNONODE, | ||
490 | 40 | Path: "/blah", | ||
491 | 41 | }, | ||
492 | 42 | `zookeeper: foo "/blah": no node`, | ||
493 | 43 | }, { | ||
494 | 44 | zk.Error{ | ||
495 | 45 | Op: "foo", | ||
496 | 46 | Code: zk.ZNONODE, | ||
497 | 47 | }, | ||
498 | 48 | `zookeeper: foo: no node`, | ||
499 | 49 | }, { | ||
500 | 50 | zk.Error{ | ||
501 | 51 | Op: "foo", | ||
502 | 52 | Code: zk.ZSYSTEMERROR, | ||
503 | 53 | SystemError: errors.New("an error"), | ||
504 | 54 | Path: "/blah", | ||
505 | 55 | }, | ||
506 | 56 | `zookeeper: foo "/blah": an error`, | ||
507 | 57 | }, { | ||
508 | 58 | zk.Error{ | ||
509 | 59 | Op: "foo", | ||
510 | 60 | Code: zk.ZSYSTEMERROR, | ||
511 | 61 | Path: "/blah", | ||
512 | 62 | }, | ||
513 | 63 | `zookeeper: foo "/blah": system error`, | ||
514 | 64 | }} | ||
515 | 65 | for _, t := range tests { | ||
516 | 66 | c.Check(t.err.Error(), Equals, t.msg) | ||
517 | 67 | } | ||
518 | 29 | } | 68 | } |
519 | 30 | 69 | ||
520 | 31 | func (s *S) TestRecvTimeoutInitParameter(c *C) { | 70 | func (s *S) TestRecvTimeoutInitParameter(c *C) { |
521 | @@ -42,7 +81,7 @@ | |||
522 | 42 | for i := 0; i != 1000; i++ { | 81 | for i := 0; i != 1000; i++ { |
523 | 43 | _, _, err := conn.Get("/zookeeper") | 82 | _, _, err := conn.Get("/zookeeper") |
524 | 44 | if err != nil { | 83 | if err != nil { |
526 | 45 | c.Assert(err, ErrorMatches, "operation timeout") | 84 | c.Check(zk.IsError(err, zk.ZOPERATIONTIMEOUT), Equals, true, Commentf("%v", err)) |
527 | 46 | c.SucceedNow() | 85 | c.SucceedNow() |
528 | 47 | } | 86 | } |
529 | 48 | } | 87 | } |
530 | @@ -158,8 +197,8 @@ | |||
531 | 158 | 197 | ||
532 | 159 | c.Assert(data, Equals, "") | 198 | c.Assert(data, Equals, "") |
533 | 160 | c.Assert(stat, IsNil) | 199 | c.Assert(stat, IsNil) |
536 | 161 | c.Assert(err, ErrorMatches, "no node") | 200 | c.Assert(err, ErrorMatches, `zookeeper: get "/non-existent": no node`) |
537 | 162 | c.Assert(err, Equals, zk.ZNONODE) | 201 | c.Check(zk.IsError(err, zk.ZNONODE), Equals, true, Commentf("%v", err)) |
538 | 163 | } | 202 | } |
539 | 164 | 203 | ||
540 | 165 | func (s *S) TestCreateAndGet(c *C) { | 204 | func (s *S) TestCreateAndGet(c *C) { |
541 | @@ -171,7 +210,7 @@ | |||
542 | 171 | 210 | ||
543 | 172 | // Check the error condition from Create(). | 211 | // Check the error condition from Create(). |
544 | 173 | _, err = conn.Create(path, "", zk.EPHEMERAL, zk.WorldACL(zk.PERM_ALL)) | 212 | _, err = conn.Create(path, "", zk.EPHEMERAL, zk.WorldACL(zk.PERM_ALL)) |
546 | 174 | c.Assert(err, ErrorMatches, "node exists") | 213 | c.Check(zk.IsError(err, zk.ZNODEEXISTS), Equals, true, Commentf("%v", err)) |
547 | 175 | 214 | ||
548 | 176 | data, _, err := conn.Get(path) | 215 | data, _, err := conn.Get(path) |
549 | 177 | c.Assert(err, IsNil) | 216 | c.Assert(err, IsNil) |
550 | @@ -270,7 +309,7 @@ | |||
551 | 270 | 309 | ||
552 | 271 | _, _, watch, err := conn.GetW("/test") | 310 | _, _, watch, err := conn.GetW("/test") |
553 | 272 | c.Assert(err, NotNil) | 311 | c.Assert(err, NotNil) |
555 | 273 | c.Assert(err, Equals, zk.ZNONODE) | 312 | c.Check(zk.IsError(err, zk.ZNONODE), Equals, true, Commentf("%v", err)) |
556 | 274 | c.Assert(watch, IsNil) | 313 | c.Assert(watch, IsNil) |
557 | 275 | 314 | ||
558 | 276 | c.Check(zk.CountPendingWatches(), Equals, 1) | 315 | c.Check(zk.CountPendingWatches(), Equals, 1) |
559 | @@ -304,7 +343,7 @@ | |||
560 | 304 | c.Assert(err, IsNil) | 343 | c.Assert(err, IsNil) |
561 | 305 | err = conn.Close() | 344 | err = conn.Close() |
562 | 306 | c.Assert(err, NotNil) | 345 | c.Assert(err, NotNil) |
564 | 307 | c.Assert(err, Equals, zk.ZCLOSING) | 346 | c.Check(zk.IsError(err, zk.ZCLOSING), Equals, true, Commentf("%v", err)) |
565 | 308 | } | 347 | } |
566 | 309 | 348 | ||
567 | 310 | func (s *S) TestChildren(c *C) { | 349 | func (s *S) TestChildren(c *C) { |
568 | @@ -316,7 +355,7 @@ | |||
569 | 316 | c.Assert(stat.NumChildren(), Equals, 1) | 355 | c.Assert(stat.NumChildren(), Equals, 1) |
570 | 317 | 356 | ||
571 | 318 | children, stat, err = conn.Children("/non-existent") | 357 | children, stat, err = conn.Children("/non-existent") |
573 | 319 | c.Assert(err, Equals, zk.ZNONODE) | 358 | c.Check(zk.IsError(err, zk.ZNONODE), Equals, true, Commentf("%v", err)) |
574 | 320 | c.Assert(children, IsNil) | 359 | c.Assert(children, IsNil) |
575 | 321 | c.Assert(stat, IsNil) | 360 | c.Assert(stat, IsNil) |
576 | 322 | } | 361 | } |
577 | @@ -383,7 +422,7 @@ | |||
578 | 383 | 422 | ||
579 | 384 | _, stat, watch, err := conn.ChildrenW("/test") | 423 | _, stat, watch, err := conn.ChildrenW("/test") |
580 | 385 | c.Assert(err, NotNil) | 424 | c.Assert(err, NotNil) |
582 | 386 | c.Assert(err, Equals, zk.ZNONODE) | 425 | c.Check(zk.IsError(err, zk.ZNONODE), Equals, true, Commentf("%v", err)) |
583 | 387 | c.Assert(watch, IsNil) | 426 | c.Assert(watch, IsNil) |
584 | 388 | c.Assert(stat, IsNil) | 427 | c.Assert(stat, IsNil) |
585 | 389 | 428 | ||
586 | @@ -447,7 +486,7 @@ | |||
587 | 447 | 486 | ||
588 | 448 | stat, watch, err := conn.ExistsW("///") | 487 | stat, watch, err := conn.ExistsW("///") |
589 | 449 | c.Assert(err, NotNil) | 488 | c.Assert(err, NotNil) |
591 | 450 | c.Assert(err, Equals, zk.ZBADARGUMENTS) | 489 | c.Check(zk.IsError(err, zk.ZBADARGUMENTS), Equals, true, Commentf("%v", err)) |
592 | 451 | c.Assert(stat, IsNil) | 490 | c.Assert(stat, IsNil) |
593 | 452 | c.Assert(watch, IsNil) | 491 | c.Assert(watch, IsNil) |
594 | 453 | 492 | ||
595 | @@ -462,14 +501,14 @@ | |||
596 | 462 | 501 | ||
597 | 463 | err = conn.Delete("/test", 5) | 502 | err = conn.Delete("/test", 5) |
598 | 464 | c.Assert(err, NotNil) | 503 | c.Assert(err, NotNil) |
600 | 465 | c.Assert(err, Equals, zk.ZBADVERSION) | 504 | c.Check(zk.IsError(err, zk.ZBADVERSION), Equals, true, Commentf("%v", err)) |
601 | 466 | 505 | ||
602 | 467 | err = conn.Delete("/test", -1) | 506 | err = conn.Delete("/test", -1) |
603 | 468 | c.Assert(err, IsNil) | 507 | c.Assert(err, IsNil) |
604 | 469 | 508 | ||
605 | 470 | err = conn.Delete("/test", -1) | 509 | err = conn.Delete("/test", -1) |
606 | 471 | c.Assert(err, NotNil) | 510 | c.Assert(err, NotNil) |
608 | 472 | c.Assert(err, Equals, zk.ZNONODE) | 511 | c.Check(zk.IsError(err, zk.ZNONODE), Equals, true, Commentf("%v", err)) |
609 | 473 | } | 512 | } |
610 | 474 | 513 | ||
611 | 475 | func (s *S) TestClientIdAndReInit(c *C) { | 514 | func (s *S) TestClientIdAndReInit(c *C) { |
612 | @@ -518,7 +557,7 @@ | |||
613 | 518 | 557 | ||
614 | 519 | acl, stat, err = conn.ACL("/non-existent") | 558 | acl, stat, err = conn.ACL("/non-existent") |
615 | 520 | c.Assert(err, NotNil) | 559 | c.Assert(err, NotNil) |
617 | 521 | c.Assert(err, Equals, zk.ZNONODE) | 560 | c.Check(zk.IsError(err, zk.ZNONODE), Equals, true, Commentf("%v", err)) |
618 | 522 | c.Assert(acl, IsNil) | 561 | c.Assert(acl, IsNil) |
619 | 523 | c.Assert(stat, IsNil) | 562 | c.Assert(stat, IsNil) |
620 | 524 | } | 563 | } |
621 | @@ -531,7 +570,7 @@ | |||
622 | 531 | 570 | ||
623 | 532 | err = conn.SetACL("/test", zk.WorldACL(zk.PERM_ALL), 5) | 571 | err = conn.SetACL("/test", zk.WorldACL(zk.PERM_ALL), 5) |
624 | 533 | c.Assert(err, NotNil) | 572 | c.Assert(err, NotNil) |
626 | 534 | c.Assert(err, Equals, zk.ZBADVERSION) | 573 | c.Check(zk.IsError(err, zk.ZBADVERSION), Equals, true, Commentf("%v", err)) |
627 | 535 | 574 | ||
628 | 536 | err = conn.SetACL("/test", zk.WorldACL(zk.PERM_READ), -1) | 575 | err = conn.SetACL("/test", zk.WorldACL(zk.PERM_READ), -1) |
629 | 537 | c.Assert(err, IsNil) | 576 | c.Assert(err, IsNil) |
630 | @@ -551,7 +590,7 @@ | |||
631 | 551 | 590 | ||
632 | 552 | _, _, err = conn.Get("/test") | 591 | _, _, err = conn.Get("/test") |
633 | 553 | c.Assert(err, NotNil) | 592 | c.Assert(err, NotNil) |
635 | 554 | c.Assert(err, Equals, zk.ZNOAUTH) | 593 | c.Check(zk.IsError(err, zk.ZNOAUTH), Equals, true, Commentf("%v", err)) |
636 | 555 | 594 | ||
637 | 556 | err = conn.AddAuth("digest", "joe:passwd") | 595 | err = conn.AddAuth("digest", "joe:passwd") |
638 | 557 | c.Assert(err, IsNil) | 596 | c.Assert(err, IsNil) |
Reviewers: mp+97613_ code.launchpad. net,
Message:
Please take a look.
Description:
https:/ /code.launchpad .net/~rogpeppe/ gozk/better- error-messages/ +merge/ 97613
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/5835045/
Affected files:
A [revision details]
M close_test.go
M retry_test.go
M zk.go
M zk_test.go