You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

117 lines
2.8 KiB

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