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 }() 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 }
//SynchronizedAfterSuite blocks complement the SynchronizedBeforeSuite blocks in solving the problem of setting up //external singleton resources shared across nodes when running tests in parallel. // //SynchronizedAfterSuite accomplishes this by taking *two* function arguments. The first runs on all nodes. The second runs only on parallel node #1 //and *only* after all other nodes have finished and exited. This ensures that node 1, and any resources it is running, remain alive until //all other nodes are finished. // //Both functions have the same signature: either func() or func(done Done) to run asynchronously. // //Here's a pseudo-code example that complements that given in SynchronizedBeforeSuite. Here, SynchronizedAfterSuite is used to tear down the shared database //only after all nodes have finished: // // var _ = SynchronizedAfterSuite(func() { // dbClient.Cleanup() // }, func() { // dbRunner.Stop() // }) func SynchronizedAfterSuite(allNodesBody interface{}, node1Body interface{}, timeout ...float64) bool { globalSuite.SetSynchronizedAfterSuiteNode( allNodesBody, node1Body, codelocation.New(1), parseTimeout(timeout...), ) return true }
//Fail notifies Ginkgo that the current spec has failed. (Gomega will call Fail for you automatically when an assertion fails.) func Fail(message string, callerSkip ...int) { skip := 0 if len(callerSkip) > 0 { skip = callerSkip[0] } globalFailer.Fail(message, codelocation.New(skip+1)) panic(GINKGO_PANIC) }
func InvalidSharedRunnerBehaviors(build func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable, componentType types.SpecComponentType) { var ( failer *Failer.Failer componentCodeLocation types.CodeLocation innerCodeLocation types.CodeLocation ) BeforeEach(func() { failer = Failer.New() componentCodeLocation = codelocation.New(0) innerCodeLocation = codelocation.New(0) }) Describe("invalid functions", func() { Context("when passed something that's not a function", func() { It("should panic", func() { Ω(func() { build("not a function", 0, failer, componentCodeLocation) }).Should(Panic()) }) }) Context("when the function takes the wrong kind of argument", func() { It("should panic", func() { Ω(func() { build(func(oops string) {}, 0, failer, componentCodeLocation) }).Should(Panic()) }) }) Context("when the function takes more than one argument", func() { It("should panic", func() { Ω(func() { build(func(done Done, oops string) {}, 0, failer, componentCodeLocation) }).Should(Panic()) }) }) }) }
func (r *runner) runSync() (outcome types.SpecState, failure types.SpecFailure) { defer func() { if e := recover(); e != nil { r.failer.Panic(codelocation.New(2), e) } failure, outcome = r.failer.Drain(r.nodeType, r.componentIndex, r.codeLocation) }() r.syncFunc() return }
//You can focus individual Its using FIt func FIt(text string, body interface{}, timeout ...float64) bool { globalSuite.PushItNode(text, body, types.FlagTypeFocused, codelocation.New(1), parseTimeout(timeout...)) return true }
//You can mark the tests within a describe block as pending using XContext func XContext(text string, body func()) bool { globalSuite.PushContainerNode(text, body, types.FlagTypePending, codelocation.New(1)) return true }
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/internal/spec" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/internal/codelocation" "github.com/onsi/ginkgo/internal/containernode" "github.com/onsi/ginkgo/internal/leafnodes" "github.com/onsi/ginkgo/types" ) var _ = Describe("Specs", func() { var specs *Specs newSpec := func(text string, flag types.FlagType) *Spec { subject := leafnodes.NewItNode(text, func() {}, flag, codelocation.New(0), 0, nil, 0) return New(subject, []*containernode.ContainerNode{}, false) } newMeasureSpec := func(text string, flag types.FlagType) *Spec { subject := leafnodes.NewMeasureNode(text, func(Benchmarker) {}, flag, codelocation.New(0), 0, nil, 0) return New(subject, []*containernode.ContainerNode{}, false) } newSpecs := func(args ...interface{}) *Specs { specs := []*Spec{} for index := 0; index < len(args)-1; index += 2 { specs = append(specs, newSpec(args[index].(string), args[index+1].(types.FlagType))) } return NewSpecs(specs) }
func SynchronousSharedRunnerBehaviors(build func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable, componentType types.SpecComponentType, componentIndex int) { var ( outcome types.SpecState failure types.SpecFailure failer *Failer.Failer componentCodeLocation types.CodeLocation innerCodeLocation types.CodeLocation didRun bool ) BeforeEach(func() { failer = Failer.New() componentCodeLocation = codelocation.New(0) innerCodeLocation = codelocation.New(0) didRun = false }) Describe("synchronous functions", func() { Context("when the function passes", func() { BeforeEach(func() { outcome, failure = build(func() { didRun = true }, 0, failer, componentCodeLocation).Run() }) It("should have a succesful outcome", func() { Ω(didRun).Should(BeTrue()) Ω(outcome).Should(Equal(types.SpecStatePassed)) Ω(failure).Should(BeZero()) }) }) Context("when a failure occurs", func() { BeforeEach(func() { outcome, failure = build(func() { didRun = true failer.Fail("bam", innerCodeLocation) panic("should not matter") }, 0, failer, componentCodeLocation).Run() }) It("should return the failure", func() { Ω(didRun).Should(BeTrue()) Ω(outcome).Should(Equal(types.SpecStateFailed)) Ω(failure).Should(Equal(types.SpecFailure{ Message: "bam", Location: innerCodeLocation, ForwardedPanic: nil, ComponentIndex: componentIndex, ComponentType: componentType, ComponentCodeLocation: componentCodeLocation, })) }) }) Context("when a panic occurs", func() { BeforeEach(func() { outcome, failure = build(func() { didRun = true innerCodeLocation = codelocation.New(0) panic("ack!") }, 0, failer, componentCodeLocation).Run() }) It("should return the panic", func() { Ω(didRun).Should(BeTrue()) Ω(outcome).Should(Equal(types.SpecStatePanicked)) innerCodeLocation.LineNumber++ Ω(failure).Should(Equal(types.SpecFailure{ Message: "Test Panicked", Location: innerCodeLocation, ForwardedPanic: "ack!", ComponentIndex: componentIndex, ComponentType: componentType, ComponentCodeLocation: componentCodeLocation, })) }) }) }) }
. "github.com/onsi/ginkgo/internal/failer" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/internal/codelocation" "github.com/onsi/ginkgo/types" ) var _ = Describe("Failer", func() { var ( failer *Failer codeLocationA types.CodeLocation codeLocationB types.CodeLocation ) BeforeEach(func() { codeLocationA = codelocation.New(0) codeLocationB = codelocation.New(0) failer = New() }) Context("with no failures", func() { It("should return success when drained", func() { failure, state := failer.Drain(types.SpecComponentTypeIt, 3, codeLocationB) Ω(failure).Should(BeZero()) Ω(state).Should(Equal(types.SpecStatePassed)) }) }) Describe("Skip", func() { It("should handle failures", func() { failer.Skip("something skipped", codeLocationA)
package leafnodes_test import ( . "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/types" . "github.com/onsi/gomega" . "github.com/onsi/ginkgo/internal/leafnodes" "github.com/onsi/ginkgo/internal/codelocation" ) var _ = Describe("Setup Nodes", func() { Describe("BeforeEachNodes", func() { It("should report the correct type and code location", func() { codeLocation := codelocation.New(0) beforeEach := NewBeforeEachNode(func() {}, codeLocation, 0, nil, 3) Ω(beforeEach.Type()).Should(Equal(types.SpecComponentTypeBeforeEach)) Ω(beforeEach.CodeLocation()).Should(Equal(codeLocation)) }) }) Describe("AfterEachNodes", func() { It("should report the correct type and code location", func() { codeLocation := codelocation.New(0) afterEach := NewAfterEachNode(func() {}, codeLocation, 0, nil, 3) Ω(afterEach.Type()).Should(Equal(types.SpecComponentTypeAfterEach)) Ω(afterEach.CodeLocation()).Should(Equal(codeLocation)) }) })
//You can mark Maeasurements as pending using XMeasure func XMeasure(text string, _ ...interface{}) bool { globalSuite.PushMeasureNode(text, func(b Benchmarker) {}, types.FlagTypePending, codelocation.New(1), 0) return true }
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/internal/spec" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/internal/codelocation" "github.com/onsi/ginkgo/internal/containernode" "github.com/onsi/ginkgo/internal/leafnodes" "github.com/onsi/ginkgo/types" ) var _ = Describe("Specs", func() { var specs *Specs newSpec := func(text string, flag types.FlagType) *Spec { subject := leafnodes.NewItNode(text, func() {}, flag, codelocation.New(0), 0, nil, 0) return New(subject, []*containernode.ContainerNode{}) } newMeasureSpec := func(text string, flag types.FlagType) *Spec { subject := leafnodes.NewMeasureNode(text, func(Benchmarker) {}, flag, codelocation.New(0), 0, nil, 0) return New(subject, []*containernode.ContainerNode{}) } newSpecs := func(args ...interface{}) *Specs { specs := []*Spec{} for index := 0; index < len(args)-1; index += 2 { specs = append(specs, newSpec(args[index].(string), args[index+1].(types.FlagType))) } return NewSpecs(specs) }
Ω(output.TestCases[0].FailureMessage).Should(BeNil()) Ω(output.TestCases[0].Skipped).Should(BeNil()) Ω(output.TestCases[0].Time).Should(Equal(5.0)) }) }) Describe("when the BeforeSuite fails", func() { var beforeSuite *types.SetupSummary BeforeEach(func() { beforeSuite = &types.SetupSummary{ State: types.SpecStateFailed, RunTime: 3 * time.Second, Failure: types.SpecFailure{ Message: "failed to setup", ComponentCodeLocation: codelocation.New(0), Location: codelocation.New(2), }, } reporter.BeforeSuiteDidRun(beforeSuite) reporter.SpecSuiteDidEnd(&types.SuiteSummary{ NumberOfSpecsThatWillBeRun: 1, NumberOfFailedSpecs: 1, RunTime: 10 * time.Second, }) }) It("should record the test as having failed", func() { output := readOutputFile() Ω(output.Tests).Should(Equal(1))
. "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/internal/codelocation" . "github.com/onsi/ginkgo/internal/containernode" "github.com/onsi/ginkgo/types" ) var _ = Describe("Container Node", func() { var ( codeLocation types.CodeLocation container *ContainerNode ) BeforeEach(func() { codeLocation = codelocation.New(0) container = New("description text", types.FlagTypeFocused, codeLocation) }) Describe("creating a container node", func() { It("can answer questions about itself", func() { Ω(container.Text()).Should(Equal("description text")) Ω(container.Flag()).Should(Equal(types.FlagTypeFocused)) Ω(container.CodeLocation()).Should(Equal(codeLocation)) }) }) Describe("pushing setup nodes", func() { It("can append setup nodes of various types and fetch them by type", func() { befA := leafnodes.NewBeforeEachNode(func() {}, codelocation.New(0), 0, nil, 0) befB := leafnodes.NewBeforeEachNode(func() {}, codelocation.New(0), 0, nil, 0)
package leafnodes_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo/internal/leafnodes" . "github.com/onsi/gomega" "github.com/onsi/ginkgo/internal/codelocation" Failer "github.com/onsi/ginkgo/internal/failer" "github.com/onsi/ginkgo/types" "time" ) var _ = Describe("Measure Nodes", func() { It("should report the correct type, text, flag, and code location", func() { codeLocation := codelocation.New(0) measure := NewMeasureNode("my measure node", func(b Benchmarker) {}, types.FlagTypeFocused, codeLocation, 10, nil, 3) Ω(measure.Type()).Should(Equal(types.SpecComponentTypeMeasure)) Ω(measure.Flag()).Should(Equal(types.FlagTypeFocused)) Ω(measure.Text()).Should(Equal("my measure node")) Ω(measure.CodeLocation()).Should(Equal(codeLocation)) Ω(measure.Samples()).Should(Equal(10)) }) Describe("benchmarking", func() { var measure *MeasureNode Describe("Value", func() { BeforeEach(func() { measure = NewMeasureNode("the measurement", func(b Benchmarker) { b.RecordValue("foo", 7, "info!")
var f = func(runText string) func() { return func() { runOrder = append(runOrder, runText) } } BeforeEach(func() { randomizeAllSpecs = false randomSeed = 11 parallelNode = 1 parallelTotal = 1 focusString = "" runOrder = make([]string, 0) specSuite.SetBeforeSuiteNode(f("BeforeSuite"), codelocation.New(0), 0) specSuite.PushBeforeEachNode(f("top BE"), codelocation.New(0), 0) specSuite.PushJustBeforeEachNode(f("top JBE"), codelocation.New(0), 0) specSuite.PushAfterEachNode(f("top AE"), codelocation.New(0), 0) specSuite.PushContainerNode("container", func() { specSuite.PushBeforeEachNode(f("BE"), codelocation.New(0), 0) specSuite.PushJustBeforeEachNode(f("JBE"), codelocation.New(0), 0) specSuite.PushAfterEachNode(f("AE"), codelocation.New(0), 0) specSuite.PushItNode("it", f("IT"), types.FlagTypeNone, codelocation.New(0), 0) specSuite.PushContainerNode("inner container", func() { specSuite.PushItNode("inner it", f("inner IT"), types.FlagTypeNone, codelocation.New(0), 0) }, types.FlagTypeNone, codelocation.New(0)) }, types.FlagTypeNone, codelocation.New(0))
) var _ = Describe("SynchronizedAfterSuiteNode", func() { var failer *Failer.Failer var node SuiteNode var codeLocation types.CodeLocation var innerCodeLocation types.CodeLocation var outcome bool var server *ghttp.Server var things []string var lock *sync.Mutex BeforeEach(func() { things = []string{} server = ghttp.NewServer() codeLocation = codelocation.New(0) innerCodeLocation = codelocation.New(0) failer = Failer.New() lock = &sync.Mutex{} }) AfterEach(func() { server.Close() }) newNode := func(bodyA interface{}, bodyB interface{}) SuiteNode { return NewSynchronizedAfterSuiteNode(bodyA, bodyB, codeLocation, time.Millisecond, failer) } ranThing := func(thing string) { lock.Lock()
//You can mark Its as pending using XIt func XIt(text string, _ ...interface{}) bool { globalSuite.PushItNode(text, func() {}, types.FlagTypePending, codelocation.New(1), 0) return true }
//You can focus individual Measures using FMeasure func FMeasure(text string, body interface{}, samples int) bool { globalSuite.PushMeasureNode(text, body, types.FlagTypeFocused, codelocation.New(1), samples) return true }
newContainer := func(text string, flag types.FlagType, setupNodes ...leafnodes.BasicNode) *containernode.ContainerNode { c := containernode.New(text, flag, codeLocation) for _, node := range setupNodes { c.PushSetupNode(node) } return c } containers := func(containers ...*containernode.ContainerNode) []*containernode.ContainerNode { return containers } BeforeEach(func() { buffer = gbytes.NewBuffer() failer = Failer.New() codeLocation = codelocation.New(0) nodesThatRan = []string{} }) Describe("marking specs focused and pending", func() { It("should satisfy various caes", func() { cases := []struct { ContainerFlags []types.FlagType SubjectFlag types.FlagType Pending bool Focused bool }{ {[]types.FlagType{}, noneFlag, false, false}, {[]types.FlagType{}, focusedFlag, false, true}, {[]types.FlagType{}, pendingFlag, true, false}, {[]types.FlagType{noneFlag}, noneFlag, false, false},
//BeforeSuite blocks are run just once before any specs are run. When running in parallel, each //parallel node process will call BeforeSuite. // //BeforeSuite blocks can be made asynchronous by providing a body function that accepts a Done channel // //You may only register *one* BeforeSuite handler per test suite. You typically do so in your bootstrap file at the top level. func BeforeSuite(body interface{}, timeout ...float64) bool { globalSuite.SetBeforeSuiteNode(body, codelocation.New(1), parseTimeout(timeout...)) return true }
reporter1 *reporters.FakeReporter reporter2 *reporters.FakeReporter failer *Failer.Failer writer *Writer.FakeGinkgoWriter thingsThatRan []string runner *SpecRunner ) newBefSuite := func(text string, fail bool) leafnodes.SuiteNode { return leafnodes.NewBeforeSuiteNode(func() { writer.AddEvent(text) thingsThatRan = append(thingsThatRan, text) if fail { failer.Fail(text, codelocation.New(0)) } }, codelocation.New(0), 0, failer) } newAftSuite := func(text string, fail bool) leafnodes.SuiteNode { return leafnodes.NewAfterSuiteNode(func() { writer.AddEvent(text) thingsThatRan = append(thingsThatRan, text) if fail { failer.Fail(text, codelocation.New(0)) } }, codelocation.New(0), 0, failer) } newSpec := func(text string, flag types.FlagType, fail bool) *spec.Spec {
//AfterEach blocks are run after It blocks. When multiple AfterEach blocks are defined in nested //Describe and Context blocks the innermost AfterEach blocks are run first. // //Like It blocks, AfterEach blocks can be made asynchronous by providing a body function that accepts //a Done channel func AfterEach(body interface{}, timeout ...float64) bool { globalSuite.PushAfterEachNode(body, codelocation.New(1), parseTimeout(timeout...)) return true }
//GinkgoRecover should be deferred at the top of any spawned goroutine that (may) call `Fail` //Since Gomega assertions call fail, you should throw a `defer GinkgoRecover()` at the top of any goroutine that //calls out to Gomega // //Here's why: Ginkgo's `Fail` method records the failure and then panics to prevent //further assertions from running. This panic must be recovered. Ginkgo does this for you //if the panic originates in a Ginkgo node (an It, BeforeEach, etc...) // //Unfortunately, if a panic originates on a goroutine *launched* from one of these nodes there's no //way for Ginkgo to rescue the panic. To do this, you must remember to `defer GinkgoRecover()` at the top of such a goroutine. func GinkgoRecover() { e := recover() if e != nil { globalFailer.Panic(codelocation.New(1), e) } }
func AsynchronousSharedRunnerBehaviors(build func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable, componentType types.SpecComponentType, componentIndex int) { var ( outcome types.SpecState failure types.SpecFailure failer *Failer.Failer componentCodeLocation types.CodeLocation innerCodeLocation types.CodeLocation didRun bool ) BeforeEach(func() { failer = Failer.New() componentCodeLocation = codelocation.New(0) innerCodeLocation = codelocation.New(0) didRun = false }) Describe("asynchronous functions", func() { var timeoutDuration time.Duration BeforeEach(func() { timeoutDuration = time.Duration(1 * float64(time.Second)) }) Context("when running", func() { It("should run the function as a goroutine, and block until it's done", func() { initialNumberOfGoRoutines := runtime.NumGoroutine() numberOfGoRoutines := 0 build(func(done Done) { didRun = true numberOfGoRoutines = runtime.NumGoroutine() close(done) }, timeoutDuration, failer, componentCodeLocation).Run() Ω(didRun).Should(BeTrue()) Ω(numberOfGoRoutines).Should(BeNumerically(">=", initialNumberOfGoRoutines+1)) }) }) Context("when the function passes", func() { BeforeEach(func() { outcome, failure = build(func(done Done) { didRun = true close(done) }, timeoutDuration, failer, componentCodeLocation).Run() }) It("should have a succesful outcome", func() { Ω(didRun).Should(BeTrue()) Ω(outcome).Should(Equal(types.SpecStatePassed)) Ω(failure).Should(BeZero()) }) }) Context("when the function fails", func() { BeforeEach(func() { outcome, failure = build(func(done Done) { didRun = true failer.Fail("bam", innerCodeLocation) time.Sleep(20 * time.Millisecond) panic("doesn't matter") close(done) }, 10*time.Millisecond, failer, componentCodeLocation).Run() }) It("should return the failure", func() { Ω(didRun).Should(BeTrue()) Ω(outcome).Should(Equal(types.SpecStateFailed)) Ω(failure).Should(Equal(types.SpecFailure{ Message: "bam", Location: innerCodeLocation, ForwardedPanic: nil, ComponentIndex: componentIndex, ComponentType: componentType, ComponentCodeLocation: componentCodeLocation, })) }) }) Context("when the function times out", func() { var guard chan struct{} BeforeEach(func() { guard = make(chan struct{}) outcome, failure = build(func(done Done) { didRun = true time.Sleep(20 * time.Millisecond) close(guard) panic("doesn't matter") close(done) }, 10*time.Millisecond, failer, componentCodeLocation).Run() }) It("should return the timeout", func() { <-guard Ω(didRun).Should(BeTrue()) Ω(outcome).Should(Equal(types.SpecStateTimedOut)) Ω(failure).Should(Equal(types.SpecFailure{ Message: "Timed out", Location: componentCodeLocation, ForwardedPanic: nil, ComponentIndex: componentIndex, ComponentType: componentType, ComponentCodeLocation: componentCodeLocation, })) }) }) Context("when the function panics", func() { BeforeEach(func() { outcome, failure = build(func(done Done) { didRun = true innerCodeLocation = codelocation.New(0) panic("ack!") }, 100*time.Millisecond, failer, componentCodeLocation).Run() }) It("should return the panic", func() { Ω(didRun).Should(BeTrue()) Ω(outcome).Should(Equal(types.SpecStatePanicked)) innerCodeLocation.LineNumber++ Ω(failure).Should(Equal(types.SpecFailure{ Message: "Test Panicked", Location: innerCodeLocation, ForwardedPanic: "ack!", ComponentIndex: componentIndex, ComponentType: componentType, ComponentCodeLocation: componentCodeLocation, })) }) }) }) }
//You can focus the tests within a describe block using FDescribe func FDescribe(text string, body func()) bool { globalSuite.PushContainerNode(text, body, types.FlagTypeFocused, codelocation.New(1)) return true }
. "github.com/onsi/ginkgo" "github.com/onsi/ginkgo/internal/codelocation" "github.com/onsi/ginkgo/types" . "github.com/onsi/gomega" "runtime" ) var _ = Describe("CodeLocation", func() { var ( codeLocation types.CodeLocation expectedFileName string expectedLineNumber int ) caller0 := func() { codeLocation = codelocation.New(1) } caller1 := func() { _, expectedFileName, expectedLineNumber, _ = runtime.Caller(0) expectedLineNumber += 2 caller0() } BeforeEach(func() { caller1() }) It("should use the passed in skip parameter to pick out the correct file & line number", func() { Ω(codeLocation.FileName).Should(Equal(expectedFileName)) Ω(codeLocation.LineNumber).Should(Equal(expectedLineNumber))