Add e2e tests

This commit is contained in:
Manuel de Brito Fontes 2017-10-17 19:50:27 -03:00
parent 99a355f25d
commit 601fb7dacf
1163 changed files with 289217 additions and 14195 deletions

View file

@ -0,0 +1,103 @@
package leafnodes
import (
"math"
"time"
"sync"
"github.com/onsi/ginkgo/types"
)
type benchmarker struct {
mu sync.Mutex
measurements map[string]*types.SpecMeasurement
orderCounter int
}
func newBenchmarker() *benchmarker {
return &benchmarker{
measurements: make(map[string]*types.SpecMeasurement, 0),
}
}
func (b *benchmarker) Time(name string, body func(), info ...interface{}) (elapsedTime time.Duration) {
t := time.Now()
body()
elapsedTime = time.Since(t)
b.mu.Lock()
defer b.mu.Unlock()
measurement := b.getMeasurement(name, "Fastest Time", "Slowest Time", "Average Time", "s", 3, info...)
measurement.Results = append(measurement.Results, elapsedTime.Seconds())
return
}
func (b *benchmarker) RecordValue(name string, value float64, info ...interface{}) {
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", "", 3, info...)
b.mu.Lock()
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
func (b *benchmarker) RecordValueWithPrecision(name string, value float64, units string, precision int, info ...interface{}) {
measurement := b.getMeasurement(name, "Smallest", " Largest", " Average", units, precision, info...)
b.mu.Lock()
defer b.mu.Unlock()
measurement.Results = append(measurement.Results, value)
}
func (b *benchmarker) getMeasurement(name string, smallestLabel string, largestLabel string, averageLabel string, units string, precision int, info ...interface{}) *types.SpecMeasurement {
measurement, ok := b.measurements[name]
if !ok {
var computedInfo interface{}
computedInfo = nil
if len(info) > 0 {
computedInfo = info[0]
}
measurement = &types.SpecMeasurement{
Name: name,
Info: computedInfo,
Order: b.orderCounter,
SmallestLabel: smallestLabel,
LargestLabel: largestLabel,
AverageLabel: averageLabel,
Units: units,
Precision: precision,
Results: make([]float64, 0),
}
b.measurements[name] = measurement
b.orderCounter++
}
return measurement
}
func (b *benchmarker) measurementsReport() map[string]*types.SpecMeasurement {
b.mu.Lock()
defer b.mu.Unlock()
for _, measurement := range b.measurements {
measurement.Smallest = math.MaxFloat64
measurement.Largest = -math.MaxFloat64
sum := float64(0)
sumOfSquares := float64(0)
for _, result := range measurement.Results {
if result > measurement.Largest {
measurement.Largest = result
}
if result < measurement.Smallest {
measurement.Smallest = result
}
sum += result
sumOfSquares += result * result
}
n := float64(len(measurement.Results))
measurement.Average = sum / n
measurement.StdDeviation = math.Sqrt(sumOfSquares/n - (sum/n)*(sum/n))
}
return b.measurements
}

View file

@ -0,0 +1,19 @@
package leafnodes
import (
"github.com/onsi/ginkgo/types"
)
type BasicNode interface {
Type() types.SpecComponentType
Run() (types.SpecState, types.SpecFailure)
CodeLocation() types.CodeLocation
}
type SubjectNode interface {
BasicNode
Text() string
Flag() types.FlagType
Samples() int
}

View file

@ -0,0 +1,46 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
type ItNode struct {
runner *runner
flag types.FlagType
text string
}
func NewItNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *ItNode {
return &ItNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeIt, componentIndex),
flag: flag,
text: text,
}
}
func (node *ItNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *ItNode) Type() types.SpecComponentType {
return types.SpecComponentTypeIt
}
func (node *ItNode) Text() string {
return node.text
}
func (node *ItNode) Flag() types.FlagType {
return node.flag
}
func (node *ItNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func (node *ItNode) Samples() int {
return 1
}

View file

@ -0,0 +1,22 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("It Nodes", func() {
It("should report the correct type, text, flag, and code location", func() {
codeLocation := codelocation.New(0)
it := NewItNode("my it node", func() {}, types.FlagTypeFocused, codeLocation, 0, nil, 3)
Ω(it.Type()).Should(Equal(types.SpecComponentTypeIt))
Ω(it.Flag()).Should(Equal(types.FlagTypeFocused))
Ω(it.Text()).Should(Equal("my it node"))
Ω(it.CodeLocation()).Should(Equal(codeLocation))
Ω(it.Samples()).Should(Equal(1))
})
})

View file

@ -0,0 +1,13 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestLeafNode(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "LeafNode Suite")
}

View file

@ -0,0 +1,61 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"reflect"
)
type MeasureNode struct {
runner *runner
text string
flag types.FlagType
samples int
benchmarker *benchmarker
}
func NewMeasureNode(text string, body interface{}, flag types.FlagType, codeLocation types.CodeLocation, samples int, failer *failer.Failer, componentIndex int) *MeasureNode {
benchmarker := newBenchmarker()
wrappedBody := func() {
reflect.ValueOf(body).Call([]reflect.Value{reflect.ValueOf(benchmarker)})
}
return &MeasureNode{
runner: newRunner(wrappedBody, codeLocation, 0, failer, types.SpecComponentTypeMeasure, componentIndex),
text: text,
flag: flag,
samples: samples,
benchmarker: benchmarker,
}
}
func (node *MeasureNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *MeasureNode) MeasurementsReport() map[string]*types.SpecMeasurement {
return node.benchmarker.measurementsReport()
}
func (node *MeasureNode) Type() types.SpecComponentType {
return types.SpecComponentTypeMeasure
}
func (node *MeasureNode) Text() string {
return node.text
}
func (node *MeasureNode) Flag() types.FlagType {
return node.flag
}
func (node *MeasureNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func (node *MeasureNode) Samples() int {
return node.samples
}

View file

@ -0,0 +1,154 @@
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!")
b.RecordValue("foo", 2)
b.RecordValue("foo", 3)
b.RecordValue("bar", 0.3)
b.RecordValue("bar", 0.1)
b.RecordValue("bar", 0.5)
b.RecordValue("bar", 0.7)
}, types.FlagTypeFocused, codelocation.New(0), 1, Failer.New(), 3)
Ω(measure.Run()).Should(Equal(types.SpecStatePassed))
})
It("records passed in values and reports on them", func() {
report := measure.MeasurementsReport()
Ω(report).Should(HaveLen(2))
Ω(report["foo"].Name).Should(Equal("foo"))
Ω(report["foo"].Info).Should(Equal("info!"))
Ω(report["foo"].Order).Should(Equal(0))
Ω(report["foo"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["foo"].LargestLabel).Should(Equal(" Largest"))
Ω(report["foo"].AverageLabel).Should(Equal(" Average"))
Ω(report["foo"].Units).Should(Equal(""))
Ω(report["foo"].Results).Should(Equal([]float64{7, 2, 3}))
Ω(report["foo"].Smallest).Should(BeNumerically("==", 2))
Ω(report["foo"].Largest).Should(BeNumerically("==", 7))
Ω(report["foo"].Average).Should(BeNumerically("==", 4))
Ω(report["foo"].StdDeviation).Should(BeNumerically("~", 2.16, 0.01))
Ω(report["bar"].Name).Should(Equal("bar"))
Ω(report["bar"].Info).Should(BeNil())
Ω(report["bar"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["bar"].Order).Should(Equal(1))
Ω(report["bar"].LargestLabel).Should(Equal(" Largest"))
Ω(report["bar"].AverageLabel).Should(Equal(" Average"))
Ω(report["bar"].Units).Should(Equal(""))
Ω(report["bar"].Results).Should(Equal([]float64{0.3, 0.1, 0.5, 0.7}))
Ω(report["bar"].Smallest).Should(BeNumerically("==", 0.1))
Ω(report["bar"].Largest).Should(BeNumerically("==", 0.7))
Ω(report["bar"].Average).Should(BeNumerically("==", 0.4))
Ω(report["bar"].StdDeviation).Should(BeNumerically("~", 0.22, 0.01))
})
})
Describe("Value with precision", func() {
BeforeEach(func() {
measure = NewMeasureNode("the measurement", func(b Benchmarker) {
b.RecordValueWithPrecision("foo", 7, "ms", 7, "info!")
b.RecordValueWithPrecision("foo", 2, "ms", 6)
b.RecordValueWithPrecision("foo", 3, "ms", 5)
b.RecordValueWithPrecision("bar", 0.3, "ns", 4)
b.RecordValueWithPrecision("bar", 0.1, "ns", 3)
b.RecordValueWithPrecision("bar", 0.5, "ns", 2)
b.RecordValueWithPrecision("bar", 0.7, "ns", 1)
}, types.FlagTypeFocused, codelocation.New(0), 1, Failer.New(), 3)
Ω(measure.Run()).Should(Equal(types.SpecStatePassed))
})
It("records passed in values and reports on them", func() {
report := measure.MeasurementsReport()
Ω(report).Should(HaveLen(2))
Ω(report["foo"].Name).Should(Equal("foo"))
Ω(report["foo"].Info).Should(Equal("info!"))
Ω(report["foo"].Order).Should(Equal(0))
Ω(report["foo"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["foo"].LargestLabel).Should(Equal(" Largest"))
Ω(report["foo"].AverageLabel).Should(Equal(" Average"))
Ω(report["foo"].Units).Should(Equal("ms"))
Ω(report["foo"].Results).Should(Equal([]float64{7, 2, 3}))
Ω(report["foo"].Smallest).Should(BeNumerically("==", 2))
Ω(report["foo"].Largest).Should(BeNumerically("==", 7))
Ω(report["foo"].Average).Should(BeNumerically("==", 4))
Ω(report["foo"].StdDeviation).Should(BeNumerically("~", 2.16, 0.01))
Ω(report["bar"].Name).Should(Equal("bar"))
Ω(report["bar"].Info).Should(BeNil())
Ω(report["bar"].SmallestLabel).Should(Equal("Smallest"))
Ω(report["bar"].Order).Should(Equal(1))
Ω(report["bar"].LargestLabel).Should(Equal(" Largest"))
Ω(report["bar"].AverageLabel).Should(Equal(" Average"))
Ω(report["bar"].Units).Should(Equal("ns"))
Ω(report["bar"].Results).Should(Equal([]float64{0.3, 0.1, 0.5, 0.7}))
Ω(report["bar"].Smallest).Should(BeNumerically("==", 0.1))
Ω(report["bar"].Largest).Should(BeNumerically("==", 0.7))
Ω(report["bar"].Average).Should(BeNumerically("==", 0.4))
Ω(report["bar"].StdDeviation).Should(BeNumerically("~", 0.22, 0.01))
})
})
Describe("Time", func() {
BeforeEach(func() {
measure = NewMeasureNode("the measurement", func(b Benchmarker) {
b.Time("foo", func() {
time.Sleep(100 * time.Millisecond)
}, "info!")
b.Time("foo", func() {
time.Sleep(200 * time.Millisecond)
})
b.Time("foo", func() {
time.Sleep(170 * time.Millisecond)
})
}, types.FlagTypeFocused, codelocation.New(0), 1, Failer.New(), 3)
Ω(measure.Run()).Should(Equal(types.SpecStatePassed))
})
It("records passed in values and reports on them", func() {
report := measure.MeasurementsReport()
Ω(report).Should(HaveLen(1))
Ω(report["foo"].Name).Should(Equal("foo"))
Ω(report["foo"].Info).Should(Equal("info!"))
Ω(report["foo"].SmallestLabel).Should(Equal("Fastest Time"))
Ω(report["foo"].LargestLabel).Should(Equal("Slowest Time"))
Ω(report["foo"].AverageLabel).Should(Equal("Average Time"))
Ω(report["foo"].Units).Should(Equal("s"))
Ω(report["foo"].Results).Should(HaveLen(3))
Ω(report["foo"].Results[0]).Should(BeNumerically("~", 0.1, 0.01))
Ω(report["foo"].Results[1]).Should(BeNumerically("~", 0.2, 0.01))
Ω(report["foo"].Results[2]).Should(BeNumerically("~", 0.17, 0.01))
Ω(report["foo"].Smallest).Should(BeNumerically("~", 0.1, 0.01))
Ω(report["foo"].Largest).Should(BeNumerically("~", 0.2, 0.01))
Ω(report["foo"].Average).Should(BeNumerically("~", 0.16, 0.01))
Ω(report["foo"].StdDeviation).Should(BeNumerically("~", 0.04, 0.01))
})
})
})
})

View file

@ -0,0 +1,113 @@
package leafnodes
import (
"fmt"
"github.com/onsi/ginkgo/internal/codelocation"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"reflect"
"time"
)
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
}()
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
}

View file

@ -0,0 +1,41 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
type SetupNode struct {
runner *runner
}
func (node *SetupNode) Run() (outcome types.SpecState, failure types.SpecFailure) {
return node.runner.run()
}
func (node *SetupNode) Type() types.SpecComponentType {
return node.runner.nodeType
}
func (node *SetupNode) CodeLocation() types.CodeLocation {
return node.runner.codeLocation
}
func NewBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeBeforeEach, componentIndex),
}
}
func NewAfterEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeAfterEach, componentIndex),
}
}
func NewJustBeforeEachNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer, componentIndex int) *SetupNode {
return &SetupNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeJustBeforeEach, componentIndex),
}
}

View file

@ -0,0 +1,40 @@
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))
})
})
Describe("JustBeforeEachNodes", func() {
It("should report the correct type and code location", func() {
codeLocation := codelocation.New(0)
justBeforeEach := NewJustBeforeEachNode(func() {}, codeLocation, 0, nil, 3)
Ω(justBeforeEach.Type()).Should(Equal(types.SpecComponentTypeJustBeforeEach))
Ω(justBeforeEach.CodeLocation()).Should(Equal(codeLocation))
})
})
})

View file

@ -0,0 +1,356 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"reflect"
"time"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
type runnable interface {
Run() (outcome types.SpecState, failure types.SpecFailure)
CodeLocation() types.CodeLocation
}
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: "",
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))
Ω(failure.ForwardedPanic).Should(Equal("ack!"))
})
})
Context("when a panic occurs with a nil value", func() {
BeforeEach(func() {
outcome, failure = build(func() {
didRun = true
innerCodeLocation = codelocation.New(0)
panic(nil)
}, 0, failer, componentCodeLocation).Run()
})
It("should return the nil-valued panic", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePanicked))
Ω(failure.ForwardedPanic).Should(Equal("<nil>"))
})
})
})
}
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() {
proveAsync := make(chan bool)
build(func(done Done) {
didRun = true
proveAsync <- true
close(done)
}, timeoutDuration, failer, componentCodeLocation).Run()
Eventually(proveAsync).Should(Receive(Equal(true)))
})
})
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: "",
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: "",
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))
Ω(failure.ForwardedPanic).Should(Equal("ack!"))
})
})
Context("when the function panics with a nil value", func() {
BeforeEach(func() {
outcome, failure = build(func(done Done) {
didRun = true
innerCodeLocation = codelocation.New(0)
panic(nil)
}, 100*time.Millisecond, failer, componentCodeLocation).Run()
})
It("should return the nil-valued panic", func() {
Ω(didRun).Should(BeTrue())
Ω(outcome).Should(Equal(types.SpecStatePanicked))
Ω(failure.ForwardedPanic).Should(Equal("<nil>"))
})
})
})
}
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
)
BeforeEach(func() {
failer = Failer.New()
componentCodeLocation = 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())
})
})
})
}
var _ = Describe("Shared RunnableNode behavior", func() {
Describe("It Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewItNode("", body, types.FlagTypeFocused, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeIt, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeIt, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeIt)
})
Describe("Measure Nodes", func() {
build := func(body interface{}, _ time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewMeasureNode("", func(Benchmarker) {
reflect.ValueOf(body).Call([]reflect.Value{})
}, types.FlagTypeFocused, componentCodeLocation, 10, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeMeasure, 3)
})
Describe("BeforeEach Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewBeforeEachNode(body, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeBeforeEach, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeBeforeEach, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeBeforeEach)
})
Describe("AfterEach Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewAfterEachNode(body, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeAfterEach, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeAfterEach, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeAfterEach)
})
Describe("JustBeforeEach Nodes", func() {
build := func(body interface{}, timeout time.Duration, failer *Failer.Failer, componentCodeLocation types.CodeLocation) runnable {
return NewJustBeforeEachNode(body, componentCodeLocation, timeout, failer, 3)
}
SynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeJustBeforeEach, 3)
AsynchronousSharedRunnerBehaviors(build, types.SpecComponentTypeJustBeforeEach, 3)
InvalidSharedRunnerBehaviors(build, types.SpecComponentTypeJustBeforeEach)
})
})

View file

@ -0,0 +1,54 @@
package leafnodes
import (
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
type SuiteNode interface {
Run(parallelNode int, parallelTotal int, syncHost string) bool
Passed() bool
Summary() *types.SetupSummary
}
type simpleSuiteNode struct {
runner *runner
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func (node *simpleSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
t := time.Now()
node.outcome, node.failure = node.runner.run()
node.runTime = time.Since(t)
return node.outcome == types.SpecStatePassed
}
func (node *simpleSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *simpleSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runner.nodeType,
CodeLocation: node.runner.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func NewBeforeSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &simpleSuiteNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0),
}
}
func NewAfterSuiteNode(body interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &simpleSuiteNode{
runner: newRunner(body, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
}
}

View file

@ -0,0 +1,230 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/ginkgo/internal/leafnodes"
"time"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
)
var _ = Describe("SuiteNodes", func() {
Describe("BeforeSuite nodes", func() {
var befSuite SuiteNode
var failer *Failer.Failer
var codeLocation types.CodeLocation
var innerCodeLocation types.CodeLocation
var outcome bool
BeforeEach(func() {
failer = Failer.New()
codeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
})
Context("when the body passes", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func() {
time.Sleep(10 * time.Millisecond)
}, codeLocation, 0, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return true when run and report as passed", func() {
Ω(outcome).Should(BeTrue())
Ω(befSuite.Passed()).Should(BeTrue())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.CodeLocation).Should(Equal(codeLocation))
Ω(summary.State).Should(Equal(types.SpecStatePassed))
Ω(summary.RunTime).Should(BeNumerically(">=", 10*time.Millisecond))
Ω(summary.Failure).Should(BeZero())
})
})
Context("when the body fails", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func() {
failer.Fail("oops", innerCodeLocation)
}, codeLocation, 0, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(befSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("oops"))
Ω(summary.Failure.Location).Should(Equal(innerCodeLocation))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body times out", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func(done Done) {
}, codeLocation, time.Millisecond, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(befSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateTimedOut))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body panics", func() {
BeforeEach(func() {
befSuite = NewBeforeSuiteNode(func() {
panic("bam")
}, codeLocation, 0, failer)
outcome = befSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(befSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := befSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStatePanicked))
Ω(summary.Failure.ForwardedPanic).Should(Equal("bam"))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
})
Describe("AfterSuite nodes", func() {
var aftSuite SuiteNode
var failer *Failer.Failer
var codeLocation types.CodeLocation
var innerCodeLocation types.CodeLocation
var outcome bool
BeforeEach(func() {
failer = Failer.New()
codeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
})
Context("when the body passes", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func() {
time.Sleep(10 * time.Millisecond)
}, codeLocation, 0, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return true when run and report as passed", func() {
Ω(outcome).Should(BeTrue())
Ω(aftSuite.Passed()).Should(BeTrue())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.CodeLocation).Should(Equal(codeLocation))
Ω(summary.State).Should(Equal(types.SpecStatePassed))
Ω(summary.RunTime).Should(BeNumerically(">=", 10*time.Millisecond))
Ω(summary.Failure).Should(BeZero())
})
})
Context("when the body fails", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func() {
failer.Fail("oops", innerCodeLocation)
}, codeLocation, 0, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(aftSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("oops"))
Ω(summary.Failure.Location).Should(Equal(innerCodeLocation))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body times out", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func(done Done) {
}, codeLocation, time.Millisecond, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(aftSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStateTimedOut))
Ω(summary.Failure.ForwardedPanic).Should(BeEmpty())
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when the body panics", func() {
BeforeEach(func() {
aftSuite = NewAfterSuiteNode(func() {
panic("bam")
}, codeLocation, 0, failer)
outcome = aftSuite.Run(0, 0, "")
})
It("should return false when run and report as failed", func() {
Ω(outcome).Should(BeFalse())
Ω(aftSuite.Passed()).Should(BeFalse())
})
It("should have the correct summary", func() {
summary := aftSuite.Summary()
Ω(summary.State).Should(Equal(types.SpecStatePanicked))
Ω(summary.Failure.ForwardedPanic).Should(Equal("bam"))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeAfterSuite))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
})
})

View file

@ -0,0 +1,89 @@
package leafnodes
import (
"encoding/json"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"io/ioutil"
"net/http"
"time"
)
type synchronizedAfterSuiteNode struct {
runnerA *runner
runnerB *runner
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func NewSynchronizedAfterSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
return &synchronizedAfterSuiteNode{
runnerA: newRunner(bodyA, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
runnerB: newRunner(bodyB, codeLocation, timeout, failer, types.SpecComponentTypeAfterSuite, 0),
}
}
func (node *synchronizedAfterSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
node.outcome, node.failure = node.runnerA.run()
if parallelNode == 1 {
if parallelTotal > 1 {
node.waitUntilOtherNodesAreDone(syncHost)
}
outcome, failure := node.runnerB.run()
if node.outcome == types.SpecStatePassed {
node.outcome, node.failure = outcome, failure
}
}
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedAfterSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedAfterSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runnerA.nodeType,
CodeLocation: node.runnerA.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func (node *synchronizedAfterSuiteNode) waitUntilOtherNodesAreDone(syncHost string) {
for {
if node.canRun(syncHost) {
return
}
time.Sleep(50 * time.Millisecond)
}
}
func (node *synchronizedAfterSuiteNode) canRun(syncHost string) bool {
resp, err := http.Get(syncHost + "/RemoteAfterSuiteData")
if err != nil || resp.StatusCode != http.StatusOK {
return false
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false
}
resp.Body.Close()
afterSuiteData := types.RemoteAfterSuiteData{}
err = json.Unmarshal(body, &afterSuiteData)
if err != nil {
return false
}
return afterSuiteData.CanRun
}

View file

@ -0,0 +1,196 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
"github.com/onsi/ginkgo/types"
. "github.com/onsi/gomega"
"sync"
"github.com/onsi/gomega/ghttp"
"net/http"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"time"
)
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()
defer lock.Unlock()
things = append(things, thing)
}
thingsThatRan := func() []string {
lock.Lock()
defer lock.Unlock()
return things
}
Context("when not running in parallel", func() {
Context("when all is well", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
})
outcome = node.Run(1, 1, server.URL())
})
It("should run A, then B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "B"}))
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
Context("when A fails", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
failer.Fail("bam", innerCodeLocation)
}, func() {
ranThing("B")
})
outcome = node.Run(1, 1, server.URL())
})
It("should still run B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "B"}))
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
Context("when B fails", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
failer.Fail("bam", innerCodeLocation)
})
outcome = node.Run(1, 1, server.URL())
})
It("should run all the things", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "B"}))
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
})
Context("when running in parallel", func() {
Context("as the first node", func() {
BeforeEach(func() {
server.AppendHandlers(ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/RemoteAfterSuiteData"),
func(writer http.ResponseWriter, request *http.Request) {
ranThing("Request1")
},
ghttp.RespondWithJSONEncoded(200, types.RemoteAfterSuiteData{false}),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/RemoteAfterSuiteData"),
func(writer http.ResponseWriter, request *http.Request) {
ranThing("Request2")
},
ghttp.RespondWithJSONEncoded(200, types.RemoteAfterSuiteData{false}),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/RemoteAfterSuiteData"),
func(writer http.ResponseWriter, request *http.Request) {
ranThing("Request3")
},
ghttp.RespondWithJSONEncoded(200, types.RemoteAfterSuiteData{true}),
))
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
})
outcome = node.Run(1, 3, server.URL())
})
It("should run A and, when the server says its time, run B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A", "Request1", "Request2", "Request3", "B"}))
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
Context("as any other node", func() {
BeforeEach(func() {
node = newNode(func() {
ranThing("A")
}, func() {
ranThing("B")
})
outcome = node.Run(2, 3, server.URL())
})
It("should run A, and not run B", func() {
Ω(thingsThatRan()).Should(Equal([]string{"A"}))
})
It("should not talk to the server", func() {
Ω(server.ReceivedRequests()).Should(BeEmpty())
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
})
})

View file

@ -0,0 +1,182 @@
package leafnodes
import (
"bytes"
"encoding/json"
"github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"io/ioutil"
"net/http"
"reflect"
"time"
)
type synchronizedBeforeSuiteNode struct {
runnerA *runner
runnerB *runner
data []byte
outcome types.SpecState
failure types.SpecFailure
runTime time.Duration
}
func NewSynchronizedBeforeSuiteNode(bodyA interface{}, bodyB interface{}, codeLocation types.CodeLocation, timeout time.Duration, failer *failer.Failer) SuiteNode {
node := &synchronizedBeforeSuiteNode{}
node.runnerA = newRunner(node.wrapA(bodyA), codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0)
node.runnerB = newRunner(node.wrapB(bodyB), codeLocation, timeout, failer, types.SpecComponentTypeBeforeSuite, 0)
return node
}
func (node *synchronizedBeforeSuiteNode) Run(parallelNode int, parallelTotal int, syncHost string) bool {
t := time.Now()
defer func() {
node.runTime = time.Since(t)
}()
if parallelNode == 1 {
node.outcome, node.failure = node.runA(parallelTotal, syncHost)
} else {
node.outcome, node.failure = node.waitForA(syncHost)
}
if node.outcome != types.SpecStatePassed {
return false
}
node.outcome, node.failure = node.runnerB.run()
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedBeforeSuiteNode) runA(parallelTotal int, syncHost string) (types.SpecState, types.SpecFailure) {
outcome, failure := node.runnerA.run()
if parallelTotal > 1 {
state := types.RemoteBeforeSuiteStatePassed
if outcome != types.SpecStatePassed {
state = types.RemoteBeforeSuiteStateFailed
}
json := (types.RemoteBeforeSuiteData{
Data: node.data,
State: state,
}).ToJSON()
http.Post(syncHost+"/BeforeSuiteState", "application/json", bytes.NewBuffer(json))
}
return outcome, failure
}
func (node *synchronizedBeforeSuiteNode) waitForA(syncHost string) (types.SpecState, types.SpecFailure) {
failure := func(message string) types.SpecFailure {
return types.SpecFailure{
Message: message,
Location: node.runnerA.codeLocation,
ComponentType: node.runnerA.nodeType,
ComponentIndex: node.runnerA.componentIndex,
ComponentCodeLocation: node.runnerA.codeLocation,
}
}
for {
resp, err := http.Get(syncHost + "/BeforeSuiteState")
if err != nil || resp.StatusCode != http.StatusOK {
return types.SpecStateFailed, failure("Failed to fetch BeforeSuite state")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return types.SpecStateFailed, failure("Failed to read BeforeSuite state")
}
resp.Body.Close()
beforeSuiteData := types.RemoteBeforeSuiteData{}
err = json.Unmarshal(body, &beforeSuiteData)
if err != nil {
return types.SpecStateFailed, failure("Failed to decode BeforeSuite state")
}
switch beforeSuiteData.State {
case types.RemoteBeforeSuiteStatePassed:
node.data = beforeSuiteData.Data
return types.SpecStatePassed, types.SpecFailure{}
case types.RemoteBeforeSuiteStateFailed:
return types.SpecStateFailed, failure("BeforeSuite on Node 1 failed")
case types.RemoteBeforeSuiteStateDisappeared:
return types.SpecStateFailed, failure("Node 1 disappeared before completing BeforeSuite")
}
time.Sleep(50 * time.Millisecond)
}
return types.SpecStateFailed, failure("Shouldn't get here!")
}
func (node *synchronizedBeforeSuiteNode) Passed() bool {
return node.outcome == types.SpecStatePassed
}
func (node *synchronizedBeforeSuiteNode) Summary() *types.SetupSummary {
return &types.SetupSummary{
ComponentType: node.runnerA.nodeType,
CodeLocation: node.runnerA.codeLocation,
State: node.outcome,
RunTime: node.runTime,
Failure: node.failure,
}
}
func (node *synchronizedBeforeSuiteNode) wrapA(bodyA interface{}) interface{} {
typeA := reflect.TypeOf(bodyA)
if typeA.Kind() != reflect.Func {
panic("SynchronizedBeforeSuite expects a function as its first argument")
}
takesNothing := typeA.NumIn() == 0
takesADoneChannel := typeA.NumIn() == 1 && typeA.In(0).Kind() == reflect.Chan && typeA.In(0).Elem().Kind() == reflect.Interface
returnsBytes := typeA.NumOut() == 1 && typeA.Out(0).Kind() == reflect.Slice && typeA.Out(0).Elem().Kind() == reflect.Uint8
if !((takesNothing || takesADoneChannel) && returnsBytes) {
panic("SynchronizedBeforeSuite's first argument should be a function that returns []byte and either takes no arguments or takes a Done channel.")
}
if takesADoneChannel {
return func(done chan<- interface{}) {
out := reflect.ValueOf(bodyA).Call([]reflect.Value{reflect.ValueOf(done)})
node.data = out[0].Interface().([]byte)
}
}
return func() {
out := reflect.ValueOf(bodyA).Call([]reflect.Value{})
node.data = out[0].Interface().([]byte)
}
}
func (node *synchronizedBeforeSuiteNode) wrapB(bodyB interface{}) interface{} {
typeB := reflect.TypeOf(bodyB)
if typeB.Kind() != reflect.Func {
panic("SynchronizedBeforeSuite expects a function as its second argument")
}
returnsNothing := typeB.NumOut() == 0
takesBytesOnly := typeB.NumIn() == 1 && typeB.In(0).Kind() == reflect.Slice && typeB.In(0).Elem().Kind() == reflect.Uint8
takesBytesAndDone := typeB.NumIn() == 2 &&
typeB.In(0).Kind() == reflect.Slice && typeB.In(0).Elem().Kind() == reflect.Uint8 &&
typeB.In(1).Kind() == reflect.Chan && typeB.In(1).Elem().Kind() == reflect.Interface
if !((takesBytesOnly || takesBytesAndDone) && returnsNothing) {
panic("SynchronizedBeforeSuite's second argument should be a function that returns nothing and either takes []byte or ([]byte, Done)")
}
if takesBytesAndDone {
return func(done chan<- interface{}) {
reflect.ValueOf(bodyB).Call([]reflect.Value{reflect.ValueOf(node.data), reflect.ValueOf(done)})
}
}
return func() {
reflect.ValueOf(bodyB).Call([]reflect.Value{reflect.ValueOf(node.data)})
}
}

View file

@ -0,0 +1,445 @@
package leafnodes_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/internal/leafnodes"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/ghttp"
"net/http"
"github.com/onsi/ginkgo/internal/codelocation"
Failer "github.com/onsi/ginkgo/internal/failer"
"github.com/onsi/ginkgo/types"
"time"
)
var _ = Describe("SynchronizedBeforeSuiteNode", func() {
var failer *Failer.Failer
var node SuiteNode
var codeLocation types.CodeLocation
var innerCodeLocation types.CodeLocation
var outcome bool
var server *ghttp.Server
BeforeEach(func() {
server = ghttp.NewServer()
codeLocation = codelocation.New(0)
innerCodeLocation = codelocation.New(0)
failer = Failer.New()
})
AfterEach(func() {
server.Close()
})
newNode := func(bodyA interface{}, bodyB interface{}) SuiteNode {
return NewSynchronizedBeforeSuiteNode(bodyA, bodyB, codeLocation, time.Millisecond, failer)
}
Describe("when not running in parallel", func() {
Context("when all is well", func() {
var data []byte
BeforeEach(func() {
data = nil
node = newNode(func() []byte {
return []byte("my data")
}, func(d []byte) {
data = d
})
outcome = node.Run(1, 1, server.URL())
})
It("should run A, then B passing the output from A to B", func() {
Ω(data).Should(Equal([]byte("my data")))
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
Ω(node.Summary().State).Should(Equal(types.SpecStatePassed))
})
})
Context("when A fails", func() {
var ranB bool
BeforeEach(func() {
ranB = false
node = newNode(func() []byte {
failer.Fail("boom", innerCodeLocation)
return nil
}, func([]byte) {
ranB = true
})
outcome = node.Run(1, 1, server.URL())
})
It("should not run B", func() {
Ω(ranB).Should(BeFalse())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
Context("when B fails", func() {
BeforeEach(func() {
node = newNode(func() []byte {
return nil
}, func([]byte) {
failer.Fail("boom", innerCodeLocation)
})
outcome = node.Run(1, 1, server.URL())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateFailed))
})
})
Context("when A times out", func() {
var ranB bool
BeforeEach(func() {
ranB = false
node = newNode(func(Done) []byte {
time.Sleep(time.Second)
return nil
}, func([]byte) {
ranB = true
})
outcome = node.Run(1, 1, server.URL())
})
It("should not run B", func() {
Ω(ranB).Should(BeFalse())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateTimedOut))
})
})
Context("when B times out", func() {
BeforeEach(func() {
node = newNode(func() []byte {
return nil
}, func([]byte, Done) {
time.Sleep(time.Second)
})
outcome = node.Run(1, 1, server.URL())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
Ω(node.Summary().State).Should(Equal(types.SpecStateTimedOut))
})
})
})
Describe("when running in parallel", func() {
var ranB bool
var parallelNode, parallelTotal int
BeforeEach(func() {
ranB = false
parallelNode, parallelTotal = 1, 3
})
Context("as the first node, it runs A", func() {
var expectedState types.RemoteBeforeSuiteData
BeforeEach(func() {
parallelNode, parallelTotal = 1, 3
})
JustBeforeEach(func() {
server.AppendHandlers(ghttp.CombineHandlers(
ghttp.VerifyRequest("POST", "/BeforeSuiteState"),
ghttp.VerifyJSONRepresenting(expectedState),
))
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
Context("when A succeeds", func() {
BeforeEach(func() {
expectedState = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStatePassed}
node = newNode(func() []byte {
return []byte("my data")
}, func([]byte) {
ranB = true
})
})
It("should post about A succeeding", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(1))
})
It("should run B", func() {
Ω(ranB).Should(BeTrue())
})
It("should report success", func() {
Ω(outcome).Should(BeTrue())
})
})
Context("when A fails", func() {
BeforeEach(func() {
expectedState = types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStateFailed}
node = newNode(func() []byte {
panic("BAM")
return []byte("my data")
}, func([]byte) {
ranB = true
})
})
It("should post about A failing", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(1))
})
It("should not run B", func() {
Ω(ranB).Should(BeFalse())
})
It("should report failure", func() {
Ω(outcome).Should(BeFalse())
})
})
})
Context("as the Nth node", func() {
var statusCode int
var response interface{}
var ranA bool
var bData []byte
BeforeEach(func() {
ranA = false
bData = nil
statusCode = http.StatusOK
server.AppendHandlers(ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/BeforeSuiteState"),
ghttp.RespondWith(http.StatusOK, string((types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}).ToJSON())),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/BeforeSuiteState"),
ghttp.RespondWith(http.StatusOK, string((types.RemoteBeforeSuiteData{nil, types.RemoteBeforeSuiteStatePending}).ToJSON())),
), ghttp.CombineHandlers(
ghttp.VerifyRequest("GET", "/BeforeSuiteState"),
ghttp.RespondWithJSONEncodedPtr(&statusCode, &response),
))
node = newNode(func() []byte {
ranA = true
return nil
}, func(data []byte) {
bData = data
})
parallelNode, parallelTotal = 2, 3
})
Context("when A on node1 succeeds", func() {
BeforeEach(func() {
response = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStatePassed}
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
It("should not run A", func() {
Ω(ranA).Should(BeFalse())
})
It("should poll for A", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(3))
})
It("should run B when the polling succeeds", func() {
Ω(bData).Should(Equal([]byte("my data")))
})
It("should succeed", func() {
Ω(outcome).Should(BeTrue())
Ω(node.Passed()).Should(BeTrue())
})
})
Context("when A on node1 fails", func() {
BeforeEach(func() {
response = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStateFailed}
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
It("should not run A", func() {
Ω(ranA).Should(BeFalse())
})
It("should poll for A", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(3))
})
It("should not run B", func() {
Ω(bData).Should(BeNil())
})
It("should fail", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
summary := node.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("BeforeSuite on Node 1 failed"))
Ω(summary.Failure.Location).Should(Equal(codeLocation))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
Context("when node1 disappears", func() {
BeforeEach(func() {
response = types.RemoteBeforeSuiteData{[]byte("my data"), types.RemoteBeforeSuiteStateDisappeared}
outcome = node.Run(parallelNode, parallelTotal, server.URL())
})
It("should not run A", func() {
Ω(ranA).Should(BeFalse())
})
It("should poll for A", func() {
Ω(server.ReceivedRequests()).Should(HaveLen(3))
})
It("should not run B", func() {
Ω(bData).Should(BeNil())
})
It("should fail", func() {
Ω(outcome).Should(BeFalse())
Ω(node.Passed()).Should(BeFalse())
summary := node.Summary()
Ω(summary.State).Should(Equal(types.SpecStateFailed))
Ω(summary.Failure.Message).Should(Equal("Node 1 disappeared before completing BeforeSuite"))
Ω(summary.Failure.Location).Should(Equal(codeLocation))
Ω(summary.Failure.ComponentType).Should(Equal(types.SpecComponentTypeBeforeSuite))
Ω(summary.Failure.ComponentIndex).Should(Equal(0))
Ω(summary.Failure.ComponentCodeLocation).Should(Equal(codeLocation))
})
})
})
})
Describe("construction", func() {
Describe("the first function", func() {
Context("when the first function returns a byte array", func() {
Context("and takes nothing", func() {
It("should be fine", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte) {})
}).ShouldNot(Panic())
})
})
Context("and takes a done function", func() {
It("should be fine", func() {
Ω(func() {
newNode(func(Done) []byte { return nil }, func([]byte) {})
}).ShouldNot(Panic())
})
})
Context("and takes more than one thing", func() {
It("should panic", func() {
Ω(func() {
newNode(func(Done, Done) []byte { return nil }, func([]byte) {})
}).Should(Panic())
})
})
Context("and takes something else", func() {
It("should panic", func() {
Ω(func() {
newNode(func(bool) []byte { return nil }, func([]byte) {})
}).Should(Panic())
})
})
})
Context("when the first function does not return a byte array", func() {
It("should panic", func() {
Ω(func() {
newNode(func() {}, func([]byte) {})
}).Should(Panic())
Ω(func() {
newNode(func() []int { return nil }, func([]byte) {})
}).Should(Panic())
})
})
})
Describe("the second function", func() {
Context("when the second function takes a byte array", func() {
It("should be fine", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte) {})
}).ShouldNot(Panic())
})
})
Context("when it also takes a done channel", func() {
It("should be fine", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte, Done) {})
}).ShouldNot(Panic())
})
})
Context("if it takes anything else", func() {
It("should panic", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte, chan bool) {})
}).Should(Panic())
Ω(func() {
newNode(func() []byte { return nil }, func(string) {})
}).Should(Panic())
})
})
Context("if it takes nothing at all", func() {
It("should panic", func() {
Ω(func() {
newNode(func() []byte { return nil }, func() {})
}).Should(Panic())
})
})
Context("if it returns something", func() {
It("should panic", func() {
Ω(func() {
newNode(func() []byte { return nil }, func([]byte) []byte { return nil })
}).Should(Panic())
})
})
})
})
})