|
|
package leafnodes
import ( "fmt" "reflect" "time"
"github.com/onsi/ginkgo/internal/codelocation" "github.com/onsi/ginkgo/internal/failer" "github.com/onsi/ginkgo/types" )
type runner struct { isAsync bool asyncFunc func(chan<- interface{}) syncFunc func() codeLocation types.CodeLocation timeoutThreshold time.Duration nodeType types.SpecComponentType componentIndex int failer *failer.Failer }
func newRunner(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, nodeType types.SpecComponentType, componentIndex int) *runner { bodyType := reflect.TypeOf(body) if bodyType.Kind() != reflect.Func { panic(fmt.Sprintf("Expected a function but got something else at %v", codeLocation)) }
runner := &runner{ codeLocation: codeLocation, timeoutThreshold: timeout, failer: failer, nodeType: nodeType, componentIndex: componentIndex, }
switch bodyType.NumIn() { case 0: runner.syncFunc = body.(func()) return runner case 1: if !(bodyType.In(0).Kind() == reflect.Chan && bodyType.In(0).Elem().Kind() == reflect.Interface) { panic(fmt.Sprintf("Must pass a Done channel to function at %v", codeLocation)) }
wrappedBody := func(done chan<- interface{}) { bodyValue := reflect.ValueOf(body) bodyValue.Call([]reflect.Value{reflect.ValueOf(done)}) }
runner.isAsync = true runner.asyncFunc = wrappedBody return runner }
panic(fmt.Sprintf("Too many arguments to function at %v", codeLocation)) }
func (r *runner) run() (outcome types.SpecState, failure types.SpecFailure) { if r.isAsync { return r.runAsync() } else { return r.runSync() } }
func (r *runner) runAsync() (outcome types.SpecState, failure types.SpecFailure) { done := make(chan interface{}, 1)
go func() { finished := false
defer func() { if e := recover(); e != nil || !finished { r.failer.Panic(codelocation.New(2), e) select { case <-done: break default: close(done) } } }()
r.asyncFunc(done) finished = true }()
// If this goroutine gets no CPU time before the select block,
// the <-done case may complete even if the test took longer than the timeoutThreshold.
// This can cause flaky behaviour, but we haven't seen it in the wild.
select { case <-done: case <-time.After(r.timeoutThreshold): r.failer.Timeout(r.codeLocation) }
failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation) return } func (r *runner) runSync() (outcome types.SpecState, failure types.SpecFailure) { finished := false
defer func() { if e := recover(); e != nil || !finished { r.failer.Panic(codelocation.New(2), e) }
failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation) }()
r.syncFunc() finished = true
return }
|