248 lines
6.7 KiB
Go
248 lines
6.7 KiB
Go
|
package spec
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"time"
|
||
|
|
||
|
"sync"
|
||
|
|
||
|
"github.com/onsi/ginkgo/internal/containernode"
|
||
|
"github.com/onsi/ginkgo/internal/leafnodes"
|
||
|
"github.com/onsi/ginkgo/types"
|
||
|
)
|
||
|
|
||
|
type Spec struct {
|
||
|
subject leafnodes.SubjectNode
|
||
|
focused bool
|
||
|
announceProgress bool
|
||
|
|
||
|
containers []*containernode.ContainerNode
|
||
|
|
||
|
state types.SpecState
|
||
|
runTime time.Duration
|
||
|
startTime time.Time
|
||
|
failure types.SpecFailure
|
||
|
previousFailures bool
|
||
|
|
||
|
stateMutex *sync.Mutex
|
||
|
}
|
||
|
|
||
|
func New(subject leafnodes.SubjectNode, containers []*containernode.ContainerNode, announceProgress bool) *Spec {
|
||
|
spec := &Spec{
|
||
|
subject: subject,
|
||
|
containers: containers,
|
||
|
focused: subject.Flag() == types.FlagTypeFocused,
|
||
|
announceProgress: announceProgress,
|
||
|
stateMutex: &sync.Mutex{},
|
||
|
}
|
||
|
|
||
|
spec.processFlag(subject.Flag())
|
||
|
for i := len(containers) - 1; i >= 0; i-- {
|
||
|
spec.processFlag(containers[i].Flag())
|
||
|
}
|
||
|
|
||
|
return spec
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) processFlag(flag types.FlagType) {
|
||
|
if flag == types.FlagTypeFocused {
|
||
|
spec.focused = true
|
||
|
} else if flag == types.FlagTypePending {
|
||
|
spec.setState(types.SpecStatePending)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Skip() {
|
||
|
spec.setState(types.SpecStateSkipped)
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Failed() bool {
|
||
|
return spec.getState() == types.SpecStateFailed || spec.getState() == types.SpecStatePanicked || spec.getState() == types.SpecStateTimedOut
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Passed() bool {
|
||
|
return spec.getState() == types.SpecStatePassed
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Flaked() bool {
|
||
|
return spec.getState() == types.SpecStatePassed && spec.previousFailures
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Pending() bool {
|
||
|
return spec.getState() == types.SpecStatePending
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Skipped() bool {
|
||
|
return spec.getState() == types.SpecStateSkipped
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Focused() bool {
|
||
|
return spec.focused
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) IsMeasurement() bool {
|
||
|
return spec.subject.Type() == types.SpecComponentTypeMeasure
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Summary(suiteID string) *types.SpecSummary {
|
||
|
componentTexts := make([]string, len(spec.containers)+1)
|
||
|
componentCodeLocations := make([]types.CodeLocation, len(spec.containers)+1)
|
||
|
|
||
|
for i, container := range spec.containers {
|
||
|
componentTexts[i] = container.Text()
|
||
|
componentCodeLocations[i] = container.CodeLocation()
|
||
|
}
|
||
|
|
||
|
componentTexts[len(spec.containers)] = spec.subject.Text()
|
||
|
componentCodeLocations[len(spec.containers)] = spec.subject.CodeLocation()
|
||
|
|
||
|
runTime := spec.runTime
|
||
|
if runTime == 0 && !spec.startTime.IsZero() {
|
||
|
runTime = time.Since(spec.startTime)
|
||
|
}
|
||
|
|
||
|
return &types.SpecSummary{
|
||
|
IsMeasurement: spec.IsMeasurement(),
|
||
|
NumberOfSamples: spec.subject.Samples(),
|
||
|
ComponentTexts: componentTexts,
|
||
|
ComponentCodeLocations: componentCodeLocations,
|
||
|
State: spec.getState(),
|
||
|
RunTime: runTime,
|
||
|
Failure: spec.failure,
|
||
|
Measurements: spec.measurementsReport(),
|
||
|
SuiteID: suiteID,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) ConcatenatedString() string {
|
||
|
s := ""
|
||
|
for _, container := range spec.containers {
|
||
|
s += container.Text() + " "
|
||
|
}
|
||
|
|
||
|
return s + spec.subject.Text()
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) Run(writer io.Writer) {
|
||
|
if spec.getState() == types.SpecStateFailed {
|
||
|
spec.previousFailures = true
|
||
|
}
|
||
|
|
||
|
spec.startTime = time.Now()
|
||
|
defer func() {
|
||
|
spec.runTime = time.Since(spec.startTime)
|
||
|
}()
|
||
|
|
||
|
for sample := 0; sample < spec.subject.Samples(); sample++ {
|
||
|
spec.runSample(sample, writer)
|
||
|
|
||
|
if spec.getState() != types.SpecStatePassed {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) getState() types.SpecState {
|
||
|
spec.stateMutex.Lock()
|
||
|
defer spec.stateMutex.Unlock()
|
||
|
return spec.state
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) setState(state types.SpecState) {
|
||
|
spec.stateMutex.Lock()
|
||
|
defer spec.stateMutex.Unlock()
|
||
|
spec.state = state
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) runSample(sample int, writer io.Writer) {
|
||
|
spec.setState(types.SpecStatePassed)
|
||
|
spec.failure = types.SpecFailure{}
|
||
|
innerMostContainerIndexToUnwind := -1
|
||
|
|
||
|
defer func() {
|
||
|
for i := innerMostContainerIndexToUnwind; i >= 0; i-- {
|
||
|
container := spec.containers[i]
|
||
|
for _, justAfterEach := range container.SetupNodesOfType(types.SpecComponentTypeJustAfterEach) {
|
||
|
spec.announceSetupNode(writer, "JustAfterEach", container, justAfterEach)
|
||
|
justAfterEachState, justAfterEachFailure := justAfterEach.Run()
|
||
|
if justAfterEachState != types.SpecStatePassed && spec.state == types.SpecStatePassed {
|
||
|
spec.state = justAfterEachState
|
||
|
spec.failure = justAfterEachFailure
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for i := innerMostContainerIndexToUnwind; i >= 0; i-- {
|
||
|
container := spec.containers[i]
|
||
|
for _, afterEach := range container.SetupNodesOfType(types.SpecComponentTypeAfterEach) {
|
||
|
spec.announceSetupNode(writer, "AfterEach", container, afterEach)
|
||
|
afterEachState, afterEachFailure := afterEach.Run()
|
||
|
if afterEachState != types.SpecStatePassed && spec.getState() == types.SpecStatePassed {
|
||
|
spec.setState(afterEachState)
|
||
|
spec.failure = afterEachFailure
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
for i, container := range spec.containers {
|
||
|
innerMostContainerIndexToUnwind = i
|
||
|
for _, beforeEach := range container.SetupNodesOfType(types.SpecComponentTypeBeforeEach) {
|
||
|
spec.announceSetupNode(writer, "BeforeEach", container, beforeEach)
|
||
|
s, f := beforeEach.Run()
|
||
|
spec.failure = f
|
||
|
spec.setState(s)
|
||
|
if spec.getState() != types.SpecStatePassed {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for _, container := range spec.containers {
|
||
|
for _, justBeforeEach := range container.SetupNodesOfType(types.SpecComponentTypeJustBeforeEach) {
|
||
|
spec.announceSetupNode(writer, "JustBeforeEach", container, justBeforeEach)
|
||
|
s, f := justBeforeEach.Run()
|
||
|
spec.failure = f
|
||
|
spec.setState(s)
|
||
|
if spec.getState() != types.SpecStatePassed {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
spec.announceSubject(writer, spec.subject)
|
||
|
s, f := spec.subject.Run()
|
||
|
spec.failure = f
|
||
|
spec.setState(s)
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) announceSetupNode(writer io.Writer, nodeType string, container *containernode.ContainerNode, setupNode leafnodes.BasicNode) {
|
||
|
if spec.announceProgress {
|
||
|
s := fmt.Sprintf("[%s] %s\n %s\n", nodeType, container.Text(), setupNode.CodeLocation().String())
|
||
|
writer.Write([]byte(s))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) announceSubject(writer io.Writer, subject leafnodes.SubjectNode) {
|
||
|
if spec.announceProgress {
|
||
|
nodeType := ""
|
||
|
switch subject.Type() {
|
||
|
case types.SpecComponentTypeIt:
|
||
|
nodeType = "It"
|
||
|
case types.SpecComponentTypeMeasure:
|
||
|
nodeType = "Measure"
|
||
|
}
|
||
|
s := fmt.Sprintf("[%s] %s\n %s\n", nodeType, subject.Text(), subject.CodeLocation().String())
|
||
|
writer.Write([]byte(s))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (spec *Spec) measurementsReport() map[string]*types.SpecMeasurement {
|
||
|
if !spec.IsMeasurement() || spec.Failed() {
|
||
|
return map[string]*types.SpecMeasurement{}
|
||
|
}
|
||
|
|
||
|
return spec.subject.(*leafnodes.MeasureNode).MeasurementsReport()
|
||
|
}
|