diff --git a/Gopkg.lock b/Gopkg.lock index f08ab4549..0426c622b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -235,6 +235,12 @@ revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" +[[projects]] + branch = "v2" + name = "gopkg.in/tomb.v2" + packages = ["."] + revision = "d5d1b5820637886def9eef33e03a27a9f166942c" + [[projects]] name = "gopkg.in/yaml.v2" packages = ["."] @@ -244,6 +250,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a270b39954b9dad18c46f097be5816ca58ae1f387940b673b387d30934ce4ed4" + inputs-digest = "44a8f2ed127a6eaa38c1449b97d298fc703c961617bd93565b89bcc6c9a41483" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/gopkg.in/tomb.v2/LICENSE b/vendor/gopkg.in/tomb.v2/LICENSE new file mode 100644 index 000000000..a4249bb31 --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/LICENSE @@ -0,0 +1,29 @@ +tomb - support for clean goroutine termination in Go. + +Copyright (c) 2010-2011 - Gustavo Niemeyer + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/gopkg.in/tomb.v2/README.md b/vendor/gopkg.in/tomb.v2/README.md new file mode 100644 index 000000000..e7f282b5a --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/README.md @@ -0,0 +1,4 @@ +Installation and usage +---------------------- + +See [gopkg.in/tomb.v2](https://gopkg.in/tomb.v2) for documentation and usage details. diff --git a/vendor/gopkg.in/tomb.v2/context.go b/vendor/gopkg.in/tomb.v2/context.go new file mode 100644 index 000000000..f0fe56f5c --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/context.go @@ -0,0 +1,74 @@ +// +build go1.7 + +package tomb + +import ( + "context" +) + +// WithContext returns a new tomb that is killed when the provided parent +// context is canceled, and a copy of parent with a replaced Done channel +// that is closed when either the tomb is dying or the parent is canceled. +// The returned context may also be obtained via the tomb's Context method. +func WithContext(parent context.Context) (*Tomb, context.Context) { + var t Tomb + t.init() + if parent.Done() != nil { + go func() { + select { + case <-t.Dying(): + case <-parent.Done(): + t.Kill(parent.Err()) + } + }() + } + t.parent = parent + child, cancel := context.WithCancel(parent) + t.addChild(parent, child, cancel) + return &t, child +} + +// Context returns a context that is a copy of the provided parent context with +// a replaced Done channel that is closed when either the tomb is dying or the +// parent is cancelled. +// +// If parent is nil, it defaults to the parent provided via WithContext, or an +// empty background parent if the tomb wasn't created via WithContext. +func (t *Tomb) Context(parent context.Context) context.Context { + t.init() + t.m.Lock() + defer t.m.Unlock() + + if parent == nil { + if t.parent == nil { + t.parent = context.Background() + } + parent = t.parent.(context.Context) + } + + if child, ok := t.child[parent]; ok { + return child.context.(context.Context) + } + + child, cancel := context.WithCancel(parent) + t.addChild(parent, child, cancel) + return child +} + +func (t *Tomb) addChild(parent context.Context, child context.Context, cancel func()) { + if t.reason != ErrStillAlive { + cancel() + return + } + if t.child == nil { + t.child = make(map[interface{}]childContext) + } + t.child[parent] = childContext{child, cancel, child.Done()} + for parent, child := range t.child { + select { + case <-child.done: + delete(t.child, parent) + default: + } + } +} diff --git a/vendor/gopkg.in/tomb.v2/context16.go b/vendor/gopkg.in/tomb.v2/context16.go new file mode 100644 index 000000000..d47d83a5a --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/context16.go @@ -0,0 +1,74 @@ +// +build !go1.7 + +package tomb + +import ( + "golang.org/x/net/context" +) + +// WithContext returns a new tomb that is killed when the provided parent +// context is canceled, and a copy of parent with a replaced Done channel +// that is closed when either the tomb is dying or the parent is canceled. +// The returned context may also be obtained via the tomb's Context method. +func WithContext(parent context.Context) (*Tomb, context.Context) { + var t Tomb + t.init() + if parent.Done() != nil { + go func() { + select { + case <-t.Dying(): + case <-parent.Done(): + t.Kill(parent.Err()) + } + }() + } + t.parent = parent + child, cancel := context.WithCancel(parent) + t.addChild(parent, child, cancel) + return &t, child +} + +// Context returns a context that is a copy of the provided parent context with +// a replaced Done channel that is closed when either the tomb is dying or the +// parent is cancelled. +// +// If parent is nil, it defaults to the parent provided via WithContext, or an +// empty background parent if the tomb wasn't created via WithContext. +func (t *Tomb) Context(parent context.Context) context.Context { + t.init() + t.m.Lock() + defer t.m.Unlock() + + if parent == nil { + if t.parent == nil { + t.parent = context.Background() + } + parent = t.parent.(context.Context) + } + + if child, ok := t.child[parent]; ok { + return child.context.(context.Context) + } + + child, cancel := context.WithCancel(parent) + t.addChild(parent, child, cancel) + return child +} + +func (t *Tomb) addChild(parent context.Context, child context.Context, cancel func()) { + if t.reason != ErrStillAlive { + cancel() + return + } + if t.child == nil { + t.child = make(map[interface{}]childContext) + } + t.child[parent] = childContext{child, cancel, child.Done()} + for parent, child := range t.child { + select { + case <-child.done: + delete(t.child, parent) + default: + } + } +} diff --git a/vendor/gopkg.in/tomb.v2/context16_test.go b/vendor/gopkg.in/tomb.v2/context16_test.go new file mode 100644 index 000000000..ad155f37d --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/context16_test.go @@ -0,0 +1,177 @@ +// +build !go1.7 + +package tomb_test + +import ( + "testing" + "time" + + "golang.org/x/net/context" + + "gopkg.in/tomb.v2" +) + +func TestWithContext(t *testing.T) { + parent1, cancel1 := context.WithCancel(context.Background()) + + tb, child1 := tomb.WithContext(parent1) + + if !tb.Alive() { + t.Fatalf("WithContext returned dead tomb") + } + if tb.Context(parent1) != child1 { + t.Fatalf("Context returned different context for same parent") + } + if tb.Context(nil) != child1 { + t.Fatalf("Context returned different context for nil parent") + } + select { + case <-child1.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + parent2, cancel2 := context.WithCancel(context.WithValue(context.Background(), "parent", "parent2")) + child2 := tb.Context(parent2) + + if tb.Context(parent2) != child2 { + t.Fatalf("Context returned different context for same parent") + } + if child2.Value("parent") != "parent2" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child2.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + cancel2() + + select { + case <-child2.Done(): + case <-time.After(5 * time.Second): + t.Fatalf("Tomb's child context didn't die after parent was canceled") + } + if !tb.Alive() { + t.Fatalf("Canceling unrelated parent context killed tomb") + } + + parent3 := context.WithValue(context.Background(), "parent", "parent3") + child3 := tb.Context(parent3) + + if child3.Value("parent") != "parent3" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child3.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + cancel1() + + select { + case <-tb.Dying(): + case <-time.After(5 * time.Second): + t.Fatalf("Canceling parent context did not kill tomb") + } + + if tb.Err() != context.Canceled { + t.Fatalf("tomb should be %v, got %v", context.Canceled, tb.Err()) + } + + if tb.Context(parent1) == child1 || tb.Context(parent3) == child3 { + t.Fatalf("Tomb is dead and shouldn't be tracking children anymore") + } + select { + case <-child3.Done(): + case <-time.After(5 * time.Second): + t.Fatalf("Child context didn't die after tomb's death") + } + + parent4 := context.WithValue(context.Background(), "parent", "parent4") + child4 := tb.Context(parent4) + + select { + case <-child4.Done(): + case <-time.After(5 * time.Second): + t.Fatalf("Child context should be born canceled") + } + + childnil := tb.Context(nil) + select { + case <-childnil.Done(): + default: + t.Fatalf("Child context should be born canceled") + } +} + +func TestContextNoParent(t *testing.T) { + var tb tomb.Tomb + + parent2, cancel2 := context.WithCancel(context.WithValue(context.Background(), "parent", "parent2")) + child2 := tb.Context(parent2) + + if tb.Context(parent2) != child2 { + t.Fatalf("Context returned different context for same parent") + } + if child2.Value("parent") != "parent2" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child2.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + cancel2() + + select { + case <-child2.Done(): + default: + t.Fatalf("Tomb's child context didn't die after parent was canceled") + } + if !tb.Alive() { + t.Fatalf("Canceling unrelated parent context killed tomb") + } + + parent3 := context.WithValue(context.Background(), "parent", "parent3") + child3 := tb.Context(parent3) + + if child3.Value("parent") != "parent3" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child3.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + tb.Kill(nil) + + if tb.Context(parent3) == child3 { + t.Fatalf("Tomb is dead and shouldn't be tracking children anymore") + } + select { + case <-child3.Done(): + default: + t.Fatalf("Child context didn't die after tomb's death") + } + + parent4 := context.WithValue(context.Background(), "parent", "parent4") + child4 := tb.Context(parent4) + + select { + case <-child4.Done(): + default: + t.Fatalf("Child context should be born canceled") + } + + childnil := tb.Context(nil) + select { + case <-childnil.Done(): + default: + t.Fatalf("Child context should be born canceled") + } +} diff --git a/vendor/gopkg.in/tomb.v2/context_test.go b/vendor/gopkg.in/tomb.v2/context_test.go new file mode 100644 index 000000000..537548386 --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/context_test.go @@ -0,0 +1,176 @@ +// +build go1.7 + +package tomb_test + +import ( + "context" + "testing" + "time" + + "gopkg.in/tomb.v2" +) + +func TestWithContext(t *testing.T) { + parent1, cancel1 := context.WithCancel(context.Background()) + + tb, child1 := tomb.WithContext(parent1) + + if !tb.Alive() { + t.Fatalf("WithContext returned dead tomb") + } + if tb.Context(parent1) != child1 { + t.Fatalf("Context returned different context for same parent") + } + if tb.Context(nil) != child1 { + t.Fatalf("Context returned different context for nil parent") + } + select { + case <-child1.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + parent2, cancel2 := context.WithCancel(context.WithValue(context.Background(), "parent", "parent2")) + child2 := tb.Context(parent2) + + if tb.Context(parent2) != child2 { + t.Fatalf("Context returned different context for same parent") + } + if child2.Value("parent") != "parent2" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child2.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + cancel2() + + select { + case <-child2.Done(): + default: + t.Fatalf("Tomb's child context didn't die after parent was canceled") + } + if !tb.Alive() { + t.Fatalf("Canceling unrelated parent context killed tomb") + } + + parent3 := context.WithValue(context.Background(), "parent", "parent3") + child3 := tb.Context(parent3) + + if child3.Value("parent") != "parent3" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child3.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + cancel1() + + select { + case <-tb.Dying(): + case <-time.After(5 * time.Second): + t.Fatalf("Canceling parent context did not kill tomb") + } + + if tb.Err() != context.Canceled { + t.Fatalf("tomb should be %v, got %v", context.Canceled, tb.Err()) + } + + if tb.Context(parent1) == child1 || tb.Context(parent3) == child3 { + t.Fatalf("Tomb is dead and shouldn't be tracking children anymore") + } + select { + case <-child3.Done(): + default: + t.Fatalf("Child context didn't die after tomb's death") + } + + parent4 := context.WithValue(context.Background(), "parent", "parent4") + child4 := tb.Context(parent4) + + select { + case <-child4.Done(): + default: + t.Fatalf("Child context should be born canceled") + } + + childnil := tb.Context(nil) + select { + case <-childnil.Done(): + default: + t.Fatalf("Child context should be born canceled") + } +} + +func TestContextNoParent(t *testing.T) { + var tb tomb.Tomb + + parent2, cancel2 := context.WithCancel(context.WithValue(context.Background(), "parent", "parent2")) + child2 := tb.Context(parent2) + + if tb.Context(parent2) != child2 { + t.Fatalf("Context returned different context for same parent") + } + if child2.Value("parent") != "parent2" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child2.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + cancel2() + + select { + case <-child2.Done(): + default: + t.Fatalf("Tomb's child context didn't die after parent was canceled") + } + if !tb.Alive() { + t.Fatalf("Canceling unrelated parent context killed tomb") + } + + parent3 := context.WithValue(context.Background(), "parent", "parent3") + child3 := tb.Context(parent3) + + if child3.Value("parent") != "parent3" { + t.Fatalf("Child context didn't inherit its parent's properties") + } + select { + case <-child3.Done(): + t.Fatalf("Tomb's child context was born dead") + default: + } + + tb.Kill(nil) + + if tb.Context(parent3) == child3 { + t.Fatalf("Tomb is dead and shouldn't be tracking children anymore") + } + select { + case <-child3.Done(): + default: + t.Fatalf("Child context didn't die after tomb's death") + } + + parent4 := context.WithValue(context.Background(), "parent", "parent4") + child4 := tb.Context(parent4) + + select { + case <-child4.Done(): + default: + t.Fatalf("Child context should be born canceled") + } + + childnil := tb.Context(nil) + select { + case <-childnil.Done(): + default: + t.Fatalf("Child context should be born canceled") + } +} diff --git a/vendor/gopkg.in/tomb.v2/tomb.go b/vendor/gopkg.in/tomb.v2/tomb.go new file mode 100644 index 000000000..069b3058b --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/tomb.go @@ -0,0 +1,237 @@ +// Copyright (c) 2011 - Gustavo Niemeyer +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// The tomb package handles clean goroutine tracking and termination. +// +// The zero value of a Tomb is ready to handle the creation of a tracked +// goroutine via its Go method, and then any tracked goroutine may call +// the Go method again to create additional tracked goroutines at +// any point. +// +// If any of the tracked goroutines returns a non-nil error, or the +// Kill or Killf method is called by any goroutine in the system (tracked +// or not), the tomb Err is set, Alive is set to false, and the Dying +// channel is closed to flag that all tracked goroutines are supposed +// to willingly terminate as soon as possible. +// +// Once all tracked goroutines terminate, the Dead channel is closed, +// and Wait unblocks and returns the first non-nil error presented +// to the tomb via a result or an explicit Kill or Killf method call, +// or nil if there were no errors. +// +// It is okay to create further goroutines via the Go method while +// the tomb is in a dying state. The final dead state is only reached +// once all tracked goroutines terminate, at which point calling +// the Go method again will cause a runtime panic. +// +// Tracked functions and methods that are still running while the tomb +// is in dying state may choose to return ErrDying as their error value. +// This preserves the well established non-nil error convention, but is +// understood by the tomb as a clean termination. The Err and Wait +// methods will still return nil if all observed errors were either +// nil or ErrDying. +// +// For background and a detailed example, see the following blog post: +// +// http://blog.labix.org/2011/10/09/death-of-goroutines-under-control +// +package tomb + +import ( + "errors" + "fmt" + "sync" +) + +// A Tomb tracks the lifecycle of one or more goroutines as alive, +// dying or dead, and the reason for their death. +// +// See the package documentation for details. +type Tomb struct { + m sync.Mutex + alive int + dying chan struct{} + dead chan struct{} + reason error + + // context.Context is available in Go 1.7+. + parent interface{} + child map[interface{}]childContext +} + +type childContext struct { + context interface{} + cancel func() + done <-chan struct{} +} + +var ( + ErrStillAlive = errors.New("tomb: still alive") + ErrDying = errors.New("tomb: dying") +) + +func (t *Tomb) init() { + t.m.Lock() + if t.dead == nil { + t.dead = make(chan struct{}) + t.dying = make(chan struct{}) + t.reason = ErrStillAlive + } + t.m.Unlock() +} + +// Dead returns the channel that can be used to wait until +// all goroutines have finished running. +func (t *Tomb) Dead() <-chan struct{} { + t.init() + return t.dead +} + +// Dying returns the channel that can be used to wait until +// t.Kill is called. +func (t *Tomb) Dying() <-chan struct{} { + t.init() + return t.dying +} + +// Wait blocks until all goroutines have finished running, and +// then returns the reason for their death. +func (t *Tomb) Wait() error { + t.init() + <-t.dead + t.m.Lock() + reason := t.reason + t.m.Unlock() + return reason +} + +// Go runs f in a new goroutine and tracks its termination. +// +// If f returns a non-nil error, t.Kill is called with that +// error as the death reason parameter. +// +// It is f's responsibility to monitor the tomb and return +// appropriately once it is in a dying state. +// +// It is safe for the f function to call the Go method again +// to create additional tracked goroutines. Once all tracked +// goroutines return, the Dead channel is closed and the +// Wait method unblocks and returns the death reason. +// +// Calling the Go method after all tracked goroutines return +// causes a runtime panic. For that reason, calling the Go +// method a second time out of a tracked goroutine is unsafe. +func (t *Tomb) Go(f func() error) { + t.init() + t.m.Lock() + defer t.m.Unlock() + select { + case <-t.dead: + panic("tomb.Go called after all goroutines terminated") + default: + } + t.alive++ + go t.run(f) +} + +func (t *Tomb) run(f func() error) { + err := f() + t.m.Lock() + defer t.m.Unlock() + t.alive-- + if t.alive == 0 || err != nil { + t.kill(err) + if t.alive == 0 { + close(t.dead) + } + } +} + +// Kill puts the tomb in a dying state for the given reason, +// closes the Dying channel, and sets Alive to false. +// +// Althoguh Kill may be called multiple times, only the first +// non-nil error is recorded as the death reason. +// +// If reason is ErrDying, the previous reason isn't replaced +// even if nil. It's a runtime error to call Kill with ErrDying +// if t is not in a dying state. +func (t *Tomb) Kill(reason error) { + t.init() + t.m.Lock() + defer t.m.Unlock() + t.kill(reason) +} + +func (t *Tomb) kill(reason error) { + if reason == ErrStillAlive { + panic("tomb: Kill with ErrStillAlive") + } + if reason == ErrDying { + if t.reason == ErrStillAlive { + panic("tomb: Kill with ErrDying while still alive") + } + return + } + if t.reason == ErrStillAlive { + t.reason = reason + close(t.dying) + for _, child := range t.child { + child.cancel() + } + t.child = nil + return + } + if t.reason == nil { + t.reason = reason + return + } +} + +// Killf calls the Kill method with an error built providing the received +// parameters to fmt.Errorf. The generated error is also returned. +func (t *Tomb) Killf(f string, a ...interface{}) error { + err := fmt.Errorf(f, a...) + t.Kill(err) + return err +} + +// Err returns the death reason, or ErrStillAlive if the tomb +// is not in a dying or dead state. +func (t *Tomb) Err() (reason error) { + t.init() + t.m.Lock() + reason = t.reason + t.m.Unlock() + return +} + +// Alive returns true if the tomb is not in a dying or dead state. +func (t *Tomb) Alive() bool { + return t.Err() == ErrStillAlive +} diff --git a/vendor/gopkg.in/tomb.v2/tomb_test.go b/vendor/gopkg.in/tomb.v2/tomb_test.go new file mode 100644 index 000000000..a1064dffe --- /dev/null +++ b/vendor/gopkg.in/tomb.v2/tomb_test.go @@ -0,0 +1,183 @@ +package tomb_test + +import ( + "errors" + "gopkg.in/tomb.v2" + "reflect" + "testing" +) + +func nothing() error { return nil } + +func TestNewTomb(t *testing.T) { + tb := &tomb.Tomb{} + checkState(t, tb, false, false, tomb.ErrStillAlive) +} + +func TestGo(t *testing.T) { + tb := &tomb.Tomb{} + alive := make(chan bool) + tb.Go(func() error { + alive <- true + tb.Go(func() error { + alive <- true + <-tb.Dying() + return nil + }) + <-tb.Dying() + return nil + }) + <-alive + <-alive + checkState(t, tb, false, false, tomb.ErrStillAlive) + tb.Kill(nil) + tb.Wait() + checkState(t, tb, true, true, nil) +} + +func TestGoErr(t *testing.T) { + first := errors.New("first error") + second := errors.New("first error") + tb := &tomb.Tomb{} + alive := make(chan bool) + tb.Go(func() error { + alive <- true + tb.Go(func() error { + alive <- true + return first + }) + <-tb.Dying() + return second + }) + <-alive + <-alive + tb.Wait() + checkState(t, tb, true, true, first) +} + +func TestGoPanic(t *testing.T) { + // ErrDying being used properly, after a clean death. + tb := &tomb.Tomb{} + tb.Go(nothing) + tb.Wait() + defer func() { + err := recover() + if err != "tomb.Go called after all goroutines terminated" { + t.Fatalf("Wrong panic on post-death tomb.Go call: %v", err) + } + checkState(t, tb, true, true, nil) + }() + tb.Go(nothing) +} + +func TestKill(t *testing.T) { + // a nil reason flags the goroutine as dying + tb := &tomb.Tomb{} + tb.Kill(nil) + checkState(t, tb, true, false, nil) + + // a non-nil reason now will override Kill + err := errors.New("some error") + tb.Kill(err) + checkState(t, tb, true, false, err) + + // another non-nil reason won't replace the first one + tb.Kill(errors.New("ignore me")) + checkState(t, tb, true, false, err) + + tb.Go(nothing) + tb.Wait() + checkState(t, tb, true, true, err) +} + +func TestKillf(t *testing.T) { + tb := &tomb.Tomb{} + + err := tb.Killf("BO%s", "OM") + if s := err.Error(); s != "BOOM" { + t.Fatalf(`Killf("BO%s", "OM"): want "BOOM", got %q`, s) + } + checkState(t, tb, true, false, err) + + // another non-nil reason won't replace the first one + tb.Killf("ignore me") + checkState(t, tb, true, false, err) + + tb.Go(nothing) + tb.Wait() + checkState(t, tb, true, true, err) +} + +func TestErrDying(t *testing.T) { + // ErrDying being used properly, after a clean death. + tb := &tomb.Tomb{} + tb.Kill(nil) + tb.Kill(tomb.ErrDying) + checkState(t, tb, true, false, nil) + + // ErrDying being used properly, after an errorful death. + err := errors.New("some error") + tb.Kill(err) + tb.Kill(tomb.ErrDying) + checkState(t, tb, true, false, err) + + // ErrDying being used badly, with an alive tomb. + tb = &tomb.Tomb{} + defer func() { + err := recover() + if err != "tomb: Kill with ErrDying while still alive" { + t.Fatalf("Wrong panic on Kill(ErrDying): %v", err) + } + checkState(t, tb, false, false, tomb.ErrStillAlive) + }() + tb.Kill(tomb.ErrDying) +} + +func TestKillErrStillAlivePanic(t *testing.T) { + tb := &tomb.Tomb{} + defer func() { + err := recover() + if err != "tomb: Kill with ErrStillAlive" { + t.Fatalf("Wrong panic on Kill(ErrStillAlive): %v", err) + } + checkState(t, tb, false, false, tomb.ErrStillAlive) + }() + tb.Kill(tomb.ErrStillAlive) +} + +func checkState(t *testing.T, tb *tomb.Tomb, wantDying, wantDead bool, wantErr error) { + select { + case <-tb.Dying(): + if !wantDying { + t.Error("<-Dying: should block") + } + default: + if wantDying { + t.Error("<-Dying: should not block") + } + } + seemsDead := false + select { + case <-tb.Dead(): + if !wantDead { + t.Error("<-Dead: should block") + } + seemsDead = true + default: + if wantDead { + t.Error("<-Dead: should not block") + } + } + if err := tb.Err(); err != wantErr { + t.Errorf("Err: want %#v, got %#v", wantErr, err) + } + if wantDead && seemsDead { + waitErr := tb.Wait() + switch { + case waitErr == tomb.ErrStillAlive: + t.Errorf("Wait should not return ErrStillAlive") + case !reflect.DeepEqual(waitErr, wantErr): + t.Errorf("Wait: want %#v, got %#v", wantErr, waitErr) + } + } +}