1package leafnodes 2 3import ( 4 "fmt" 5 "reflect" 6 "time" 7 8 "github.com/onsi/ginkgo/internal/codelocation" 9 "github.com/onsi/ginkgo/internal/failer" 10 "github.com/onsi/ginkgo/types" 11) 12 13type runner struct { 14 isAsync bool 15 asyncFunc func(chan<- interface{}) 16 syncFunc func() 17 codeLocation types.CodeLocation 18 timeoutThreshold time.Duration 19 nodeType types.SpecComponentType 20 componentIndex int 21 failer *failer.Failer 22} 23 24func newRunner(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, nodeType types.SpecComponentType, componentIndex int) *runner { 25 bodyType := reflect.TypeOf(body) 26 if bodyType.Kind() != reflect.Func { 27 panic(fmt.Sprintf("Expected a function but got something else at %v", codeLocation)) 28 } 29 30 runner := &runner{ 31 codeLocation: codeLocation, 32 timeoutThreshold: timeout, 33 failer: failer, 34 nodeType: nodeType, 35 componentIndex: componentIndex, 36 } 37 38 switch bodyType.NumIn() { 39 case 0: 40 runner.syncFunc = body.(func()) 41 return runner 42 case 1: 43 if !(bodyType.In(0).Kind() == reflect.Chan && bodyType.In(0).Elem().Kind() == reflect.Interface) { 44 panic(fmt.Sprintf("Must pass a Done channel to function at %v", codeLocation)) 45 } 46 47 wrappedBody := func(done chan<- interface{}) { 48 bodyValue := reflect.ValueOf(body) 49 bodyValue.Call([]reflect.Value{reflect.ValueOf(done)}) 50 } 51 52 runner.isAsync = true 53 runner.asyncFunc = wrappedBody 54 return runner 55 } 56 57 panic(fmt.Sprintf("Too many arguments to function at %v", codeLocation)) 58} 59 60func (r *runner) run() (outcome types.SpecState, failure types.SpecFailure) { 61 if r.isAsync { 62 return r.runAsync() 63 } else { 64 return r.runSync() 65 } 66} 67 68func (r *runner) runAsync() (outcome types.SpecState, failure types.SpecFailure) { 69 done := make(chan interface{}, 1) 70 71 go func() { 72 finished := false 73 74 defer func() { 75 if e := recover(); e != nil || !finished { 76 r.failer.Panic(codelocation.New(2), e) 77 select { 78 case <-done: 79 break 80 default: 81 close(done) 82 } 83 } 84 }() 85 86 r.asyncFunc(done) 87 finished = true 88 }() 89 90 // If this goroutine gets no CPU time before the select block, 91 // the <-done case may complete even if the test took longer than the timeoutThreshold. 92 // This can cause flaky behaviour, but we haven't seen it in the wild. 93 select { 94 case <-done: 95 case <-time.After(r.timeoutThreshold): 96 r.failer.Timeout(r.codeLocation) 97 } 98 99 failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation) 100 return 101} 102func (r *runner) runSync() (outcome types.SpecState, failure types.SpecFailure) { 103 finished := false 104 105 defer func() { 106 if e := recover(); e != nil || !finished { 107 r.failer.Panic(codelocation.New(2), e) 108 } 109 110 failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation) 111 }() 112 113 r.syncFunc() 114 finished = true 115 116 return 117} 118