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 }