From 8d3aa3bce781ff2afb2d78b9709ea4573375ce2a Mon Sep 17 00:00:00 2001 From: Simon Richardson Date: Fri, 29 Sep 2023 16:05:03 +0100 Subject: [PATCH] Expose the worker names The following exposes the worker names for a given runner. This is useful to iterate over the workers. This doesn't check to see if the workers are not dead/found, it just reports the actual workers available to it. This rationale for this, is to prevent the divergent sources of truth. The runner knows exactly which runners it is tracking and removes them after death. Attempting to track when each runner is dead from the outside isn't that easy, if some can crash internally or be removed from the runner directly. Instead of playing a guessing game, just look it up. --- runner.go | 14 ++++++++++++++ runner_test.go | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/runner.go b/runner.go index a1ceac6..b4fb6ee 100644 --- a/runner.go +++ b/runner.go @@ -303,6 +303,20 @@ func (runner *Runner) Worker(id string, abort <-chan struct{}) (Worker, error) { return w, err } +// WorkerNames returns the names of the current workers. +// They are returned in no particular order and they might not exists when +// the Worker request is made. +func (runner *Runner) WorkerNames() []string { + runner.mu.Lock() + defer runner.mu.Unlock() + + names := make([]string, 0, len(runner.workers)) + for name := range runner.workers { + names = append(names, name) + } + return names +} + func (runner *Runner) workerInfo(id string, abort <-chan struct{}) (Worker, <-chan struct{}, error) { runner.mu.Lock() // getWorker returns the current worker for the id diff --git a/runner_test.go b/runner_test.go index cce1ca2..01c00b7 100644 --- a/runner_test.go +++ b/runner_test.go @@ -43,9 +43,11 @@ func (*RunnerSuite) TestOneWorkerStart(c *gc.C) { err := runner.StartWorker("id", starter.start) c.Assert(err, jc.ErrorIsNil) starter.assertStarted(c, true) + c.Assert(runner.WorkerNames(), jc.SameContents, []string{"id"}) c.Assert(worker.Stop(runner), gc.IsNil) starter.assertStarted(c, false) + c.Assert(runner.WorkerNames(), jc.SameContents, []string{}) } func (*RunnerSuite) TestOneWorkerFinish(c *gc.C) { @@ -63,6 +65,7 @@ func (*RunnerSuite) TestOneWorkerFinish(c *gc.C) { starter.assertNeverStarted(c, time.Millisecond) c.Assert(worker.Stop(runner), gc.IsNil) + c.Assert(runner.WorkerNames(), jc.SameContents, []string{}) } func (*RunnerSuite) TestOneWorkerRestart(c *gc.C) { @@ -361,6 +364,7 @@ func (*RunnerSuite) TestStartWorkerWhenDead(c *gc.C) { }) c.Assert(worker.Stop(runner), gc.IsNil) c.Assert(runner.StartWorker("foo", nil), gc.Equals, worker.ErrDead) + c.Assert(runner.WorkerNames(), jc.SameContents, []string{}) } func (*RunnerSuite) TestStopWorkerWhenDead(c *gc.C) { @@ -370,6 +374,7 @@ func (*RunnerSuite) TestStopWorkerWhenDead(c *gc.C) { }) c.Assert(worker.Stop(runner), gc.IsNil) c.Assert(runner.StopWorker("foo"), gc.Equals, worker.ErrDead) + c.Assert(runner.WorkerNames(), jc.SameContents, []string{}) } func (*RunnerSuite) TestAllWorkersStoppedWhenOneDiesWithFatalError(c *gc.C) { @@ -384,8 +389,9 @@ func (*RunnerSuite) TestAllWorkersStoppedWhenOneDiesWithFatalError(c *gc.C) { c.Assert(err, jc.ErrorIsNil) starters = append(starters, starter) } - for _, starter := range starters { + for i, starter := range starters { starter.assertStarted(c, true) + c.Assert(runner.WorkerNames(), Contains, fmt.Sprint(i)) } dieErr := errors.New("fatal error") starters[4].die <- dieErr @@ -394,6 +400,7 @@ func (*RunnerSuite) TestAllWorkersStoppedWhenOneDiesWithFatalError(c *gc.C) { for _, starter := range starters { starter.assertStarted(c, false) } + c.Assert(runner.WorkerNames(), jc.SameContents, []string{}) } func (*RunnerSuite) TestFatalErrorWhileStarting(c *gc.C) { @@ -489,6 +496,7 @@ func (*RunnerSuite) TestWorkerWithNoWorker(c *gc.C) { w, err := runner.Worker("id", nil) c.Assert(err, jc.Satisfies, errors.IsNotFound) c.Assert(w, gc.Equals, nil) + c.Assert(runner.WorkerNames(), jc.SameContents, []string{}) } func (*RunnerSuite) TestWorkerWithWorkerImmediatelyAvailable(c *gc.C) { @@ -944,3 +952,32 @@ func noneFatal(error) bool { func allFatal(error) bool { return true } + +// containsChecker checks that a slice of strings contains a given string. +type containsChecker struct { + *gc.CheckerInfo +} + +// Contains checks that a slice of strings contains a given string. +var Contains gc.Checker = &containsChecker{ + CheckerInfo: &gc.CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}}, +} + +func (checker *containsChecker) Check(params []interface{}, names []string) (bool, string) { + expected, ok := params[1].(string) + if !ok { + return false, "expected must be a string" + } + + obtained, isSlice := params[0].([]string) + if isSlice { + for _, s := range obtained { + if s == expected { + return true, "" + } + } + return false, "" + } + + return false, "Obtained value is not a []string" +}