Merge lp:~thumper/juju-core/refactor-uniter-tests into lp:~go-bot/juju-core/trunk

Proposed by Tim Penhey
Status: Merged
Approved by: Tim Penhey
Approved revision: no longer in the source branch.
Merged at revision: 2141
Proposed branch: lp:~thumper/juju-core/refactor-uniter-tests
Merge into: lp:~go-bot/juju-core/trunk
Prerequisite: lp:~thumper/juju-core/juju-run-listener
Diff against target: 510 lines (+185/-112)
5 files modified
worker/uniter/context.go (+33/-16)
worker/uniter/context_test.go (+7/-2)
worker/uniter/export_test.go (+8/-0)
worker/uniter/uniter.go (+112/-48)
worker/uniter/uniter_test.go (+25/-46)
To merge this branch: bzr merge lp:~thumper/juju-core/refactor-uniter-tests
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+198668@code.launchpad.net

Commit message

Refactor uniter tests

We needed to have a better way to match the hooks that had been executed by
the uniter in the tests. Instead of relying on log messages that the hooks
wrote out, instead we have the uniter optionally call out to an observer.

Part of this work also involved breaking up some of the larger methods in the
Context and the Uniter methods. These broken up bits are used by the code
that runs generic commands coming shortly.

A fundamental change is that now if a hook is missing, instead of silently
passing, the context now returns a missingHookError.

https://codereview.appspot.com/41210043/

Description of the change

Refactor uniter tests

We needed to have a better way to match the hooks that had been executed by
the uniter in the tests. Instead of relying on log messages that the hooks
wrote out, instead we have the uniter optionally call out to an observer.

Part of this work also involved breaking up some of the larger methods in the
Context and the Uniter methods. These broken up bits are used by the code
that runs generic commands coming shortly.

A fundamental change is that now if a hook is missing, instead of silently
passing, the context now returns a missingHookError.

https://codereview.appspot.com/41210043/

To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :

Reviewers: mp+198668_code.launchpad.net,

Message:
Please take a look.

Description:
Refactor uniter tests

We needed to have a better way to match the hooks that had been executed
by
the uniter in the tests. Instead of relying on log messages that the
hooks
wrote out, instead we have the uniter optionally call out to an
observer.

Part of this work also involved breaking up some of the larger methods
in the
Context and the Uniter methods. These broken up bits are used by the
code
that runs generic commands coming shortly.

A fundamental change is that now if a hook is missing, instead of
silently
passing, the context now returns a missingHookError.

https://code.launchpad.net/~thumper/juju-core/refactor-uniter-tests/+merge/198668

Requires:
https://code.launchpad.net/~thumper/juju-core/juju-run-listener/+merge/198662

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/41210043/

Affected files (+181, -109 lines):
   A [revision details]
   M worker/uniter/context.go
   M worker/uniter/context_test.go
   A worker/uniter/export_test.go
   M worker/uniter/uniter.go
   M worker/uniter/uniter_test.go

Revision history for this message
Ian Booth (wallyworld) wrote :

LGTM with some tweaks

https://codereview.appspot.com/41210043/diff/1/worker/uniter/context.go
File worker/uniter/context.go (right):

https://codereview.appspot.com/41210043/diff/1/worker/uniter/context.go#newcode191
worker/uniter/context.go:191: writeChanges := err == nil
Why not just make writeChanges a method param?
And set it accordingly in the caller if the err the caller has is nil?

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter.go
File worker/uniter/uniter.go (right):

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter.go#newcode35
worker/uniter/uniter.go:35: type UniterExecutionObserver interface {
Doc string please

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter_test.go
File worker/uniter/uniter_test.go (left):

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter_test.go#oldcode192
worker/uniter/uniter_test.go:192: func (ctx *context) matchLogHooks(c
*gc.C) (match bool, overshoot bool) {
Is this still the best method name?

https://codereview.appspot.com/41210043/

Revision history for this message
Tim Penhey (thumper) wrote :

Please take a look.

https://codereview.appspot.com/41210043/diff/1/worker/uniter/context.go
File worker/uniter/context.go (right):

https://codereview.appspot.com/41210043/diff/1/worker/uniter/context.go#newcode191
worker/uniter/context.go:191: writeChanges := err == nil
On 2013/12/13 00:23:11, wallyworld wrote:
> Why not just make writeChanges a method param?
> And set it accordingly in the caller if the err the caller has is nil?

Because doing it this way makes the caller much cleaner, and we need the
error anyway as we return that if it is not-nil.

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter.go
File worker/uniter/uniter.go (right):

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter.go#newcode35
worker/uniter/uniter.go:35: type UniterExecutionObserver interface {
On 2013/12/13 00:23:11, wallyworld wrote:
> Doc string please

Done.

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter_test.go
File worker/uniter/uniter_test.go (left):

https://codereview.appspot.com/41210043/diff/1/worker/uniter/uniter_test.go#oldcode192
worker/uniter/uniter_test.go:192: func (ctx *context) matchLogHooks(c
*gc.C) (match bool, overshoot bool) {
On 2013/12/13 00:23:11, wallyworld wrote:
> Is this still the best method name?

Haha, ah, no. I'll change to something more meaningful.

https://codereview.appspot.com/41210043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'worker/uniter/context.go'
--- worker/uniter/context.go 2013-10-04 17:38:30 +0000
+++ worker/uniter/context.go 2013-12-13 02:48:10 +0000
@@ -22,6 +22,19 @@
22 "launchpad.net/juju-core/worker/uniter/jujuc"22 "launchpad.net/juju-core/worker/uniter/jujuc"
23)23)
2424
25type missingHookError struct {
26 hookName string
27}
28
29func (e *missingHookError) Error() string {
30 return e.hookName + " does not exist"
31}
32
33func IsMissingHookError(err error) bool {
34 _, ok := err.(*missingHookError)
35 return ok
36}
37
25// HookContext is the implementation of jujuc.Context.38// HookContext is the implementation of jujuc.Context.
26type HookContext struct {39type HookContext struct {
27 unit *uniter.Unit40 unit *uniter.Unit
@@ -174,25 +187,14 @@
174 return vars187 return vars
175}188}
176189
177// RunHook executes a hook in an environment which allows it to to call back190func (ctx *HookContext) finalizeContext(process string, err error) error {
178// into ctx to execute jujuc tools.191 writeChanges := err == nil
179func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath string) error {
180 var err error
181 env := ctx.hookVars(charmDir, toolsDir, socketPath)
182 debugctx := unitdebug.NewHooksContext(ctx.unit.Name())
183 if session, _ := debugctx.FindSession(); session != nil && session.MatchHook(hookName) {
184 logger.Infof("executing %s via debug-hooks", hookName)
185 err = session.RunHook(hookName, charmDir, env)
186 } else {
187 err = runCharmHook(hookName, charmDir, env)
188 }
189 write := err == nil
190 for id, rctx := range ctx.relations {192 for id, rctx := range ctx.relations {
191 if write {193 if writeChanges {
192 if e := rctx.WriteSettings(); e != nil {194 if e := rctx.WriteSettings(); e != nil {
193 e = fmt.Errorf(195 e = fmt.Errorf(
194 "could not write settings from %q to relation %d: %v",196 "could not write settings from %q to relation %d: %v",
195 hookName, id, e,197 process, id, e,
196 )198 )
197 logger.Errorf("%v", e)199 logger.Errorf("%v", e)
198 if err == nil {200 if err == nil {
@@ -205,6 +207,21 @@
205 return err207 return err
206}208}
207209
210// RunHook executes a hook in an environment which allows it to to call back
211// into ctx to execute jujuc tools.
212func (ctx *HookContext) RunHook(hookName, charmDir, toolsDir, socketPath string) error {
213 var err error
214 env := ctx.hookVars(charmDir, toolsDir, socketPath)
215 debugctx := unitdebug.NewHooksContext(ctx.unit.Name())
216 if session, _ := debugctx.FindSession(); session != nil && session.MatchHook(hookName) {
217 logger.Infof("executing %s via debug-hooks", hookName)
218 err = session.RunHook(hookName, charmDir, env)
219 } else {
220 err = runCharmHook(hookName, charmDir, env)
221 }
222 return ctx.finalizeContext(hookName, err)
223}
224
208func runCharmHook(hookName, charmDir string, env []string) error {225func runCharmHook(hookName, charmDir string, env []string) error {
209 ps := exec.Command(filepath.Join(charmDir, "hooks", hookName))226 ps := exec.Command(filepath.Join(charmDir, "hooks", hookName))
210 ps.Env = env227 ps.Env = env
@@ -230,7 +247,7 @@
230 if os.IsNotExist(ee.Err) {247 if os.IsNotExist(ee.Err) {
231 // Missing hook is perfectly valid, but worth mentioning.248 // Missing hook is perfectly valid, but worth mentioning.
232 logger.Infof("skipped %q hook (not implemented)", hookName)249 logger.Infof("skipped %q hook (not implemented)", hookName)
233 return nil250 return &missingHookError{hookName}
234 }251 }
235 }252 }
236 return err253 return err
237254
=== modified file 'worker/uniter/context_test.go'
--- worker/uniter/context_test.go 2013-11-14 12:32:14 +0000
+++ worker/uniter/context_test.go 2013-12-13 02:48:10 +0000
@@ -19,6 +19,7 @@
19 "launchpad.net/juju-core/state/api"19 "launchpad.net/juju-core/state/api"
20 "launchpad.net/juju-core/state/api/params"20 "launchpad.net/juju-core/state/api/params"
21 apiuniter "launchpad.net/juju-core/state/api/uniter"21 apiuniter "launchpad.net/juju-core/state/api/uniter"
22 jc "launchpad.net/juju-core/testing/checkers"
22 "launchpad.net/juju-core/utils"23 "launchpad.net/juju-core/utils"
23 "launchpad.net/juju-core/worker/uniter"24 "launchpad.net/juju-core/worker/uniter"
24 "launchpad.net/juju-core/worker/uniter/jujuc"25 "launchpad.net/juju-core/worker/uniter/jujuc"
@@ -206,9 +207,10 @@
206 uuid, err := utils.NewUUID()207 uuid, err := utils.NewUUID()
207 c.Assert(err, gc.IsNil)208 c.Assert(err, gc.IsNil)
208 for i, t := range runHookTests {209 for i, t := range runHookTests {
209 c.Logf("test %d: %s; perm %v", i, t.summary, t.spec.perm)210 c.Logf("\ntest %d: %s; perm %v", i, t.summary, t.spec.perm)
210 ctx := s.GetHookContext(c, uuid.String(), t.relid, t.remote)211 ctx := s.GetHookContext(c, uuid.String(), t.relid, t.remote)
211 var charmDir, outPath string212 var charmDir, outPath string
213 var hookExists bool
212 if t.spec.perm == 0 {214 if t.spec.perm == 0 {
213 charmDir = c.MkDir()215 charmDir = c.MkDir()
214 } else {216 } else {
@@ -216,12 +218,15 @@
216 spec.name = "something-happened"218 spec.name = "something-happened"
217 c.Logf("makeCharm %#v", spec)219 c.Logf("makeCharm %#v", spec)
218 charmDir, outPath = makeCharm(c, spec)220 charmDir, outPath = makeCharm(c, spec)
221 hookExists = true
219 }222 }
220 toolsDir := c.MkDir()223 toolsDir := c.MkDir()
221 t0 := time.Now()224 t0 := time.Now()
222 err := ctx.RunHook("something-happened", charmDir, toolsDir, "/path/to/socket")225 err := ctx.RunHook("something-happened", charmDir, toolsDir, "/path/to/socket")
223 if t.err == "" {226 if t.err == "" && hookExists {
224 c.Assert(err, gc.IsNil)227 c.Assert(err, gc.IsNil)
228 } else if !hookExists {
229 c.Assert(uniter.IsMissingHookError(err), jc.IsTrue)
225 } else {230 } else {
226 c.Assert(err, gc.ErrorMatches, t.err)231 c.Assert(err, gc.ErrorMatches, t.err)
227 }232 }
228233
=== added file 'worker/uniter/export_test.go'
--- worker/uniter/export_test.go 1970-01-01 00:00:00 +0000
+++ worker/uniter/export_test.go 2013-12-13 02:48:10 +0000
@@ -0,0 +1,8 @@
1// Copyright 2013 Canonical Ltd.
2// Licensed under the AGPLv3, see LICENCE file for details.
3
4package uniter
5
6func SetUniterObserver(u *Uniter, observer UniterExecutionObserver) {
7 u.observer = observer
8}
09
=== modified file 'worker/uniter/uniter.go'
--- worker/uniter/uniter.go 2013-11-14 12:32:14 +0000
+++ worker/uniter/uniter.go 2013-12-13 02:48:10 +0000
@@ -32,6 +32,14 @@
3232
33var logger = loggo.GetLogger("juju.worker.uniter")33var logger = loggo.GetLogger("juju.worker.uniter")
3434
35// A UniterExecutionObserver gets the appropriate methods called when a hook
36// is executed and either succeeds or fails. Missing hooks don't get reported
37// in this way.
38type UniterExecutionObserver interface {
39 HookCompleted(hookName string)
40 HookFailed(hookName string)
41}
42
35// Uniter implements the capabilities of the unit agent. It is not intended to43// Uniter implements the capabilities of the unit agent. It is not intended to
36// implement the actual *behaviour* of the unit agent; that responsibility is44// implement the actual *behaviour* of the unit agent; that responsibility is
37// delegated to Mode values, which are expected to react to events and direct45// delegated to Mode values, which are expected to react to events and direct
@@ -59,6 +67,9 @@
59 hookLock *fslock.Lock67 hookLock *fslock.Lock
6068
61 ranConfigChanged bool69 ranConfigChanged bool
70 // The execution observer is only used in tests at this stage. Should this
71 // need to be extended, perhaps a list of observers would be needed.
72 observer UniterExecutionObserver
62}73}
6374
64// NewUniter creates a new Uniter which will install, run, and upgrade75// NewUniter creates a new Uniter which will install, run, and upgrade
@@ -274,23 +285,26 @@
274// operation is not affected by the error.285// operation is not affected by the error.
275var errHookFailed = stderrors.New("hook execution failed")286var errHookFailed = stderrors.New("hook execution failed")
276287
277// runHook executes the supplied hook.Info in an appropriate hook context. If288func (u *Uniter) getHookContext(hctxId string, relationId int, remoteUnitName string) (context *HookContext, err error) {
278// the hook itself fails to execute, it returns errHookFailed.289
279func (u *Uniter) runHook(hi hook.Info) (err error) {290 apiAddrs, err := u.st.APIAddresses()
280 // Prepare context.291 if err != nil {
281 if err = hi.Validate(); err != nil {292 return nil, err
282 return err293 }
283 }294 ownerTag, err := u.service.GetOwnerTag()
284295 if err != nil {
285 hookName := string(hi.Kind)296 return nil, err
286 relationId := -1297 }
287 if hi.Kind.IsRelation() {298 ctxRelations := map[int]*ContextRelation{}
288 relationId = hi.RelationId299 for id, r := range u.relationers {
289 if hookName, err = u.relationers[relationId].PrepareHook(hi); err != nil {300 ctxRelations[id] = r.Context()
290 return err301 }
291 }302
292 }303 return NewHookContext(u.unit, hctxId, u.uuid, relationId, remoteUnitName,
293 hctxId := fmt.Sprintf("%s:%s:%d", u.unit.Name(), hookName, u.rand.Int63())304 ctxRelations, apiAddrs, ownerTag)
305}
306
307func (u *Uniter) acquireHookLock(message string) (err error) {
294 // We want to make sure we don't block forever when locking, but take the308 // We want to make sure we don't block forever when locking, but take the
295 // tomb into account.309 // tomb into account.
296 checkTomb := func() error {310 checkTomb := func() error {
@@ -302,48 +316,88 @@
302 }316 }
303 return nil317 return nil
304 }318 }
305 lockMessage := fmt.Sprintf("%s: running hook %q", u.unit.Name(), hookName)319 if err = u.hookLock.LockWithFunc(message, checkTomb); err != nil {
306 if err = u.hookLock.LockWithFunc(lockMessage, checkTomb); err != nil {320 return err
307 return err321 }
308 }322 return nil
309 defer u.hookLock.Unlock()323}
310324
311 ctxRelations := map[int]*ContextRelation{}325func (u *Uniter) startJujucServer(context *HookContext) (*jujuc.Server, string, error) {
312 for id, r := range u.relationers {
313 ctxRelations[id] = r.Context()
314 }
315 apiAddrs, err := u.st.APIAddresses()
316 if err != nil {
317 return err
318 }
319
320 ownerTag, err := u.service.GetOwnerTag()
321 if err != nil {
322 return err
323 }
324 hctx, err := NewHookContext(u.unit, hctxId, u.uuid, relationId, hi.RemoteUnit,
325 ctxRelations, apiAddrs, ownerTag)
326 if err != nil {
327 return err
328 }
329
330 // Prepare server.326 // Prepare server.
331 getCmd := func(ctxId, cmdName string) (cmd.Command, error) {327 getCmd := func(ctxId, cmdName string) (cmd.Command, error) {
332 // TODO: switch to long-running server with single context;328 // TODO: switch to long-running server with single context;
333 // use nonce in place of context id.329 // use nonce in place of context id.
334 if ctxId != hctxId {330 if ctxId != context.id {
335 return nil, fmt.Errorf("expected context id %q, got %q", hctxId, ctxId)331 return nil, fmt.Errorf("expected context id %q, got %q", context.id, ctxId)
336 }332 }
337 return jujuc.NewCommand(hctx, cmdName)333 return jujuc.NewCommand(context, cmdName)
338 }334 }
339 socketPath := filepath.Join(u.baseDir, "agent.socket")335 socketPath := filepath.Join(u.baseDir, "agent.socket")
340 // Use abstract namespace so we don't get stale socket files.336 // Use abstract namespace so we don't get stale socket files.
341 socketPath = "@" + socketPath337 socketPath = "@" + socketPath
342 srv, err := jujuc.NewServer(getCmd, socketPath)338 srv, err := jujuc.NewServer(getCmd, socketPath)
343 if err != nil {339 if err != nil {
344 return err340 return nil, "", err
345 }341 }
346 go srv.Run()342 go srv.Run()
343 return srv, socketPath, nil
344}
345
346func (u *Uniter) notifyHookInternal(hook string, hctx *HookContext, method func(string)) {
347 if r, ok := hctx.HookRelation(); ok {
348 remote, _ := hctx.RemoteUnitName()
349 if remote != "" {
350 remote = " " + remote
351 }
352 hook = hook + remote + " " + r.FakeId()
353 }
354 method(hook)
355}
356
357func (u *Uniter) notifyHookCompleted(hook string, hctx *HookContext) {
358 if u.observer != nil {
359 u.notifyHookInternal(hook, hctx, u.observer.HookCompleted)
360 }
361}
362
363func (u *Uniter) notifyHookFailed(hook string, hctx *HookContext) {
364 if u.observer != nil {
365 u.notifyHookInternal(hook, hctx, u.observer.HookFailed)
366 }
367}
368
369// runHook executes the supplied hook.Info in an appropriate hook context. If
370// the hook itself fails to execute, it returns errHookFailed.
371func (u *Uniter) runHook(hi hook.Info) (err error) {
372 // Prepare context.
373 if err = hi.Validate(); err != nil {
374 return err
375 }
376
377 hookName := string(hi.Kind)
378 relationId := -1
379 if hi.Kind.IsRelation() {
380 relationId = hi.RelationId
381 if hookName, err = u.relationers[relationId].PrepareHook(hi); err != nil {
382 return err
383 }
384 }
385 hctxId := fmt.Sprintf("%s:%s:%d", u.unit.Name(), hookName, u.rand.Int63())
386
387 lockMessage := fmt.Sprintf("%s: running hook %q", u.unit.Name(), hookName)
388 if err = u.acquireHookLock(lockMessage); err != nil {
389 return err
390 }
391 defer u.hookLock.Unlock()
392
393 hctx, err := u.getHookContext(hctxId, relationId, hi.RemoteUnit)
394 if err != nil {
395 return err
396 }
397 srv, socketPath, err := u.startJujucServer(hctx)
398 if err != nil {
399 return err
400 }
347 defer srv.Close()401 defer srv.Close()
348402
349 // Run the hook.403 // Run the hook.
@@ -351,14 +405,24 @@
351 return err405 return err
352 }406 }
353 logger.Infof("running %q hook", hookName)407 logger.Infof("running %q hook", hookName)
354 if err := hctx.RunHook(hookName, u.charm.Path(), u.toolsDir, socketPath); err != nil {408 ranHook := true
409 err = hctx.RunHook(hookName, u.charm.Path(), u.toolsDir, socketPath)
410 if IsMissingHookError(err) {
411 ranHook = false
412 } else if err != nil {
355 logger.Errorf("hook failed: %s", err)413 logger.Errorf("hook failed: %s", err)
414 u.notifyHookFailed(hookName, hctx)
356 return errHookFailed415 return errHookFailed
357 }416 }
358 if err := u.writeState(RunHook, Done, &hi, nil); err != nil {417 if err := u.writeState(RunHook, Done, &hi, nil); err != nil {
359 return err418 return err
360 }419 }
361 logger.Infof("ran %q hook", hookName)420 if ranHook {
421 logger.Infof("ran %q hook", hookName)
422 u.notifyHookCompleted(hookName, hctx)
423 } else {
424 logger.Infof("skipped %q hook (missing)", hookName)
425 }
362 return u.commitHook(hi)426 return u.commitHook(hi)
363}427}
364428
365429
=== modified file 'worker/uniter/uniter_test.go'
--- worker/uniter/uniter_test.go 2013-11-07 09:09:55 +0000
+++ worker/uniter/uniter_test.go 2013-12-13 02:48:10 +0000
@@ -14,9 +14,7 @@
14 "os"14 "os"
15 "os/exec"15 "os/exec"
16 "path/filepath"16 "path/filepath"
17 "regexp"
18 "strconv"17 "strconv"
19 "strings"
20 stdtesting "testing"18 stdtesting "testing"
21 "time"19 "time"
2220
@@ -32,7 +30,7 @@
32 "launchpad.net/juju-core/state/api/params"30 "launchpad.net/juju-core/state/api/params"
33 apiuniter "launchpad.net/juju-core/state/api/uniter"31 apiuniter "launchpad.net/juju-core/state/api/uniter"
34 coretesting "launchpad.net/juju-core/testing"32 coretesting "launchpad.net/juju-core/testing"
35 "launchpad.net/juju-core/testing/checkers"33 jc "launchpad.net/juju-core/testing/checkers"
36 "launchpad.net/juju-core/utils"34 "launchpad.net/juju-core/utils"
37 "launchpad.net/juju-core/utils/fslock"35 "launchpad.net/juju-core/utils/fslock"
38 "launchpad.net/juju-core/worker"36 "launchpad.net/juju-core/worker"
@@ -153,6 +151,18 @@
153 relation *state.Relation151 relation *state.Relation
154 relationUnits map[string]*state.RelationUnit152 relationUnits map[string]*state.RelationUnit
155 subordinate *state.Unit153 subordinate *state.Unit
154
155 hooksCompleted []string
156}
157
158var _ uniter.UniterExecutionObserver = (*context)(nil)
159
160func (ctx *context) HookCompleted(hookName string) {
161 ctx.hooksCompleted = append(ctx.hooksCompleted, hookName)
162}
163
164func (ctx *context) HookFailed(hookName string) {
165 ctx.hooksCompleted = append(ctx.hooksCompleted, "fail-"+hookName)
156}166}
157167
158func (ctx *context) run(c *gc.C, steps []stepper) {168func (ctx *context) run(c *gc.C, steps []stepper) {
@@ -189,49 +199,17 @@
189 c.Assert(err, gc.IsNil)199 c.Assert(err, gc.IsNil)
190}200}
191201
192func (ctx *context) matchLogHooks(c *gc.C) (match bool, overshoot bool) {202func (ctx *context) matchHooks(c *gc.C) (match bool, overshoot bool) {
193 // TODO(thumper): fix this to actually use a log writer to catch interesting log messages.203 c.Logf("ctx.hooksCompleted: %#v", ctx.hooksCompleted)
194 // hookPattern matches juju-log calls as generated by writeHook.204 if len(ctx.hooksCompleted) < len(ctx.hooks) {
195 hookPattern := fmt.Sprintf(`^.* INFO juju `+
196 `u/0(| [a-z0-9-]+:[0-9]+)`+ // juju-log badge; group matches relation id
197 `: %s`+ // JUJU_ENV_UUID (context badge; prevents cross-test pollution)
198 ` ([0-9a-z-/ ]+)$`, // foo-relation-joined bar/123
199 ctx.uuid,
200 )
201 // donePattern matches uniter logging that indicates a hook has run.
202 donePattern := `^.* (INFO|ERROR) juju\.worker\.uniter (ran "[a-z0-9-]+" hook|hook failed)`
203 hookRegexp := regexp.MustCompile(hookPattern)
204 doneRegexp := regexp.MustCompile(donePattern)
205
206 // pending is empty while we scan for a new hook, and holds a value while
207 // we scan for output indicating that hook execution has finished; at which
208 // point, we add it to...
209 pending := ""
210 // ...actual, which holds a list of executed hooks, with basic information
211 // about the relation context, and which will be compared against ctx's
212 // complete list of expected hooks.
213 var actual []string
214 for _, line := range strings.Split(c.GetTestLog(), "\n") {
215 if pending == "" {
216 if parts := hookRegexp.FindStringSubmatch(line); parts != nil {
217 // "hook-name[ JUJU_REMOTE_UNIT]" + "[ JUJU_RELATION_ID]
218 pending = parts[2] + parts[1]
219 }
220 } else if doneRegexp.MatchString(line) {
221 actual = append(actual, pending)
222 pending = ""
223 }
224 }
225 c.Logf("actual: %#v", actual)
226 if len(actual) < len(ctx.hooks) {
227 return false, false205 return false, false
228 }206 }
229 for i, e := range ctx.hooks {207 for i, e := range ctx.hooks {
230 if actual[i] != e {208 if ctx.hooksCompleted[i] != e {
231 return false, false209 return false, false
232 }210 }
233 }211 }
234 return true, len(actual) > len(ctx.hooks)212 return true, len(ctx.hooksCompleted) > len(ctx.hooks)
235}213}
236214
237var startupTests = []uniterTest{215var startupTests = []uniterTest{
@@ -827,7 +805,7 @@
827805
828 // ignore should not (only in v1)806 // ignore should not (only in v1)
829 _, err = os.Stat(filepath.Join(ctx.path, "charm", "ignore"))807 _, err = os.Stat(filepath.Join(ctx.path, "charm", "ignore"))
830 c.Assert(err, checkers.Satisfies, os.IsNotExist)808 c.Assert(err, jc.Satisfies, os.IsNotExist)
831809
832 // data should contain what was written in the start hook810 // data should contain what was written in the start hook
833 data, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "data"))811 data, err := ioutil.ReadFile(filepath.Join(ctx.path, "charm", "data"))
@@ -1257,6 +1235,7 @@
1257 panic("API connection not established")1235 panic("API connection not established")
1258 }1236 }
1259 ctx.uniter = uniter.NewUniter(ctx.s.uniter, s.unitTag, ctx.dataDir)1237 ctx.uniter = uniter.NewUniter(ctx.s.uniter, s.unitTag, ctx.dataDir)
1238 uniter.SetUniterObserver(ctx.uniter, ctx)
1260}1239}
12611240
1262type waitUniterDead struct {1241type waitUniterDead struct {
@@ -1487,7 +1466,7 @@
1487 }1466 }
1488 ctx.hooks = append(ctx.hooks, s...)1467 ctx.hooks = append(ctx.hooks, s...)
1489 c.Logf("waiting for hooks: %#v", ctx.hooks)1468 c.Logf("waiting for hooks: %#v", ctx.hooks)
1490 match, overshoot := ctx.matchLogHooks(c)1469 match, overshoot := ctx.matchHooks(c)
1491 if overshoot && len(s) == 0 {1470 if overshoot && len(s) == 0 {
1492 c.Fatalf("ran more hooks than expected")1471 c.Fatalf("ran more hooks than expected")
1493 }1472 }
@@ -1499,7 +1478,7 @@
1499 ctx.s.BackingState.StartSync()1478 ctx.s.BackingState.StartSync()
1500 select {1479 select {
1501 case <-time.After(coretesting.ShortWait):1480 case <-time.After(coretesting.ShortWait):
1502 if match, _ = ctx.matchLogHooks(c); match {1481 if match, _ = ctx.matchHooks(c); match {
1503 return1482 return
1504 }1483 }
1505 case <-timeout:1484 case <-timeout:
@@ -1680,7 +1659,7 @@
1680func (s relationState) step(c *gc.C, ctx *context) {1659func (s relationState) step(c *gc.C, ctx *context) {
1681 err := ctx.relation.Refresh()1660 err := ctx.relation.Refresh()
1682 if s.removed {1661 if s.removed {
1683 c.Assert(err, checkers.Satisfies, errors.IsNotFoundError)1662 c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
1684 return1663 return
1685 }1664 }
1686 c.Assert(err, gc.IsNil)1665 c.Assert(err, gc.IsNil)
@@ -1908,10 +1887,10 @@
19081887
1909var verifyHookSyncLockUnlocked = custom{func(c *gc.C, ctx *context) {1888var verifyHookSyncLockUnlocked = custom{func(c *gc.C, ctx *context) {
1910 lock := createHookLock(c, ctx.dataDir)1889 lock := createHookLock(c, ctx.dataDir)
1911 c.Assert(lock.IsLocked(), gc.Equals, false)1890 c.Assert(lock.IsLocked(), jc.IsFalse)
1912}}1891}}
19131892
1914var verifyHookSyncLockLocked = custom{func(c *gc.C, ctx *context) {1893var verifyHookSyncLockLocked = custom{func(c *gc.C, ctx *context) {
1915 lock := createHookLock(c, ctx.dataDir)1894 lock := createHookLock(c, ctx.dataDir)
1916 c.Assert(lock, checkers.Satisfies, (*fslock.Lock).IsLocked)1895 c.Assert(lock.IsLocked(), jc.IsTrue)
1917}}1896}}

Subscribers

People subscribed via source and target branches

to status/vote changes: