...

Source file src/github.com/onsi/ginkgo/v2/types/types.go

Documentation: github.com/onsi/ginkgo/v2/types

     1  package types
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  )
    10  
    11  const GINKGO_FOCUS_EXIT_CODE = 197
    12  const GINKGO_TIME_FORMAT = "01/02/06 15:04:05.999"
    13  
    14  // Report captures information about a Ginkgo test run
    15  type Report struct {
    16  	//SuitePath captures the absolute path to the test suite
    17  	SuitePath string
    18  
    19  	//SuiteDescription captures the description string passed to the DSL's RunSpecs() function
    20  	SuiteDescription string
    21  
    22  	//SuiteLabels captures any labels attached to the suite by the DSL's RunSpecs() function
    23  	SuiteLabels []string
    24  
    25  	//SuiteSucceeded captures the success or failure status of the test run
    26  	//If true, the test run is considered successful.
    27  	//If false, the test run is considered unsuccessful
    28  	SuiteSucceeded bool
    29  
    30  	//SuiteHasProgrammaticFocus captures whether the test suite has a test or set of tests that are programmatically focused
    31  	//(i.e an `FIt` or an `FDescribe`
    32  	SuiteHasProgrammaticFocus bool
    33  
    34  	//SpecialSuiteFailureReasons may contain special failure reasons
    35  	//For example, a test suite might be considered "failed" even if none of the individual specs
    36  	//have a failure state.  For example, if the user has configured --fail-on-pending the test suite
    37  	//will have failed if there are pending tests even though all non-pending tests may have passed.  In such
    38  	//cases, Ginkgo populates SpecialSuiteFailureReasons with a clear message indicating the reason for the failure.
    39  	//SpecialSuiteFailureReasons is also populated if the test suite is interrupted by the user.
    40  	//Since multiple special failure reasons can occur, this field is a slice.
    41  	SpecialSuiteFailureReasons []string
    42  
    43  	//PreRunStats contains a set of stats captured before the test run begins.  This is primarily used
    44  	//by Ginkgo's reporter to tell the user how many specs are in the current suite (PreRunStats.TotalSpecs)
    45  	//and how many it intends to run (PreRunStats.SpecsThatWillRun) after applying any relevant focus or skip filters.
    46  	PreRunStats PreRunStats
    47  
    48  	//StartTime and EndTime capture the start and end time of the test run
    49  	StartTime time.Time
    50  	EndTime   time.Time
    51  
    52  	//RunTime captures the duration of the test run
    53  	RunTime time.Duration
    54  
    55  	//SuiteConfig captures the Ginkgo configuration governing this test run
    56  	//SuiteConfig includes information necessary for reproducing an identical test run,
    57  	//such as the random seed and any filters applied during the test run
    58  	SuiteConfig SuiteConfig
    59  
    60  	//SpecReports is a list of all SpecReports generated by this test run
    61  	//It is empty when the SuiteReport is provided to ReportBeforeSuite
    62  	SpecReports SpecReports
    63  }
    64  
    65  // PreRunStats contains a set of stats captured before the test run begins.  This is primarily used
    66  // by Ginkgo's reporter to tell the user how many specs are in the current suite (PreRunStats.TotalSpecs)
    67  // and how many it intends to run (PreRunStats.SpecsThatWillRun) after applying any relevant focus or skip filters.
    68  type PreRunStats struct {
    69  	TotalSpecs       int
    70  	SpecsThatWillRun int
    71  }
    72  
    73  // Add is used by Ginkgo's parallel aggregation mechanisms to combine test run reports form individual parallel processes
    74  // to form a complete final report.
    75  func (report Report) Add(other Report) Report {
    76  	report.SuiteSucceeded = report.SuiteSucceeded && other.SuiteSucceeded
    77  
    78  	if other.StartTime.Before(report.StartTime) {
    79  		report.StartTime = other.StartTime
    80  	}
    81  
    82  	if other.EndTime.After(report.EndTime) {
    83  		report.EndTime = other.EndTime
    84  	}
    85  
    86  	specialSuiteFailureReasons := []string{}
    87  	reasonsLookup := map[string]bool{}
    88  	for _, reasons := range [][]string{report.SpecialSuiteFailureReasons, other.SpecialSuiteFailureReasons} {
    89  		for _, reason := range reasons {
    90  			if !reasonsLookup[reason] {
    91  				reasonsLookup[reason] = true
    92  				specialSuiteFailureReasons = append(specialSuiteFailureReasons, reason)
    93  			}
    94  		}
    95  	}
    96  	report.SpecialSuiteFailureReasons = specialSuiteFailureReasons
    97  	report.RunTime = report.EndTime.Sub(report.StartTime)
    98  
    99  	reports := make(SpecReports, len(report.SpecReports)+len(other.SpecReports))
   100  	copy(reports, report.SpecReports)
   101  	offset := len(report.SpecReports)
   102  	for i := range other.SpecReports {
   103  		reports[i+offset] = other.SpecReports[i]
   104  	}
   105  
   106  	report.SpecReports = reports
   107  	return report
   108  }
   109  
   110  // SpecReport captures information about a Ginkgo spec.
   111  type SpecReport struct {
   112  	// ContainerHierarchyTexts is a slice containing the text strings of
   113  	// all Describe/Context/When containers in this spec's hierarchy.
   114  	ContainerHierarchyTexts []string
   115  
   116  	// ContainerHierarchyLocations is a slice containing the CodeLocations of
   117  	// all Describe/Context/When containers in this spec's hierarchy.
   118  	ContainerHierarchyLocations []CodeLocation
   119  
   120  	// ContainerHierarchyLabels is a slice containing the labels of
   121  	// all Describe/Context/When containers in this spec's hierarchy
   122  	ContainerHierarchyLabels [][]string
   123  
   124  	// LeafNodeType, LeadNodeLocation, LeafNodeLabels and LeafNodeText capture the NodeType, CodeLocation, and text
   125  	// of the Ginkgo node being tested (typically an NodeTypeIt node, though this can also be
   126  	// one of the NodeTypesForSuiteLevelNodes node types)
   127  	LeafNodeType     NodeType
   128  	LeafNodeLocation CodeLocation
   129  	LeafNodeLabels   []string
   130  	LeafNodeText     string
   131  
   132  	// State captures whether the spec has passed, failed, etc.
   133  	State SpecState
   134  
   135  	// IsSerial captures whether the spec has the Serial decorator
   136  	IsSerial bool
   137  
   138  	// IsInOrderedContainer captures whether the spec appears in an Ordered container
   139  	IsInOrderedContainer bool
   140  
   141  	// StartTime and EndTime capture the start and end time of the spec
   142  	StartTime time.Time
   143  	EndTime   time.Time
   144  
   145  	// RunTime captures the duration of the spec
   146  	RunTime time.Duration
   147  
   148  	// ParallelProcess captures the parallel process that this spec ran on
   149  	ParallelProcess int
   150  
   151  	// RunningInParallel captures whether this spec is part of a suite that ran in parallel
   152  	RunningInParallel bool
   153  
   154  	//Failure is populated if a spec has failed, panicked, been interrupted, or skipped by the user (e.g. calling Skip())
   155  	//It includes detailed information about the Failure
   156  	Failure Failure
   157  
   158  	// NumAttempts captures the number of times this Spec was run.
   159  	// Flakey specs can be retried with ginkgo --flake-attempts=N or the use of the FlakeAttempts decorator.
   160  	// Repeated specs can be retried with the use of the MustPassRepeatedly decorator
   161  	NumAttempts int
   162  
   163  	// MaxFlakeAttempts captures whether the spec has been retried with ginkgo --flake-attempts=N or the use of the FlakeAttempts decorator.
   164  	MaxFlakeAttempts int
   165  
   166  	// MaxMustPassRepeatedly captures whether the spec has the MustPassRepeatedly decorator
   167  	MaxMustPassRepeatedly int
   168  
   169  	// CapturedGinkgoWriterOutput contains text printed to the GinkgoWriter
   170  	CapturedGinkgoWriterOutput string
   171  
   172  	// CapturedStdOutErr contains text printed to stdout/stderr (when running in parallel)
   173  	// This is always empty when running in series or calling CurrentSpecReport()
   174  	// It is used internally by Ginkgo's reporter
   175  	CapturedStdOutErr string
   176  
   177  	// ReportEntries contains any reports added via `AddReportEntry`
   178  	ReportEntries ReportEntries
   179  
   180  	// ProgressReports contains any progress reports generated during this spec.  These can either be manually triggered, or automatically generated by Ginkgo via the PollProgressAfter() decorator
   181  	ProgressReports []ProgressReport
   182  
   183  	// AdditionalFailures contains any failures that occurred after the initial spec failure.  These typically occur in cleanup nodes after the initial failure and are only emitted when running in verbose mode.
   184  	AdditionalFailures []AdditionalFailure
   185  
   186  	// SpecEvents capture additional events that occur during the spec run
   187  	SpecEvents SpecEvents
   188  }
   189  
   190  func (report SpecReport) MarshalJSON() ([]byte, error) {
   191  	//All this to avoid emitting an empty Failure struct in the JSON
   192  	out := struct {
   193  		ContainerHierarchyTexts     []string
   194  		ContainerHierarchyLocations []CodeLocation
   195  		ContainerHierarchyLabels    [][]string
   196  		LeafNodeType                NodeType
   197  		LeafNodeLocation            CodeLocation
   198  		LeafNodeLabels              []string
   199  		LeafNodeText                string
   200  		State                       SpecState
   201  		StartTime                   time.Time
   202  		EndTime                     time.Time
   203  		RunTime                     time.Duration
   204  		ParallelProcess             int
   205  		Failure                     *Failure `json:",omitempty"`
   206  		NumAttempts                 int
   207  		MaxFlakeAttempts            int
   208  		MaxMustPassRepeatedly       int
   209  		CapturedGinkgoWriterOutput  string              `json:",omitempty"`
   210  		CapturedStdOutErr           string              `json:",omitempty"`
   211  		ReportEntries               ReportEntries       `json:",omitempty"`
   212  		ProgressReports             []ProgressReport    `json:",omitempty"`
   213  		AdditionalFailures          []AdditionalFailure `json:",omitempty"`
   214  		SpecEvents                  SpecEvents          `json:",omitempty"`
   215  	}{
   216  		ContainerHierarchyTexts:     report.ContainerHierarchyTexts,
   217  		ContainerHierarchyLocations: report.ContainerHierarchyLocations,
   218  		ContainerHierarchyLabels:    report.ContainerHierarchyLabels,
   219  		LeafNodeType:                report.LeafNodeType,
   220  		LeafNodeLocation:            report.LeafNodeLocation,
   221  		LeafNodeLabels:              report.LeafNodeLabels,
   222  		LeafNodeText:                report.LeafNodeText,
   223  		State:                       report.State,
   224  		StartTime:                   report.StartTime,
   225  		EndTime:                     report.EndTime,
   226  		RunTime:                     report.RunTime,
   227  		ParallelProcess:             report.ParallelProcess,
   228  		Failure:                     nil,
   229  		ReportEntries:               nil,
   230  		NumAttempts:                 report.NumAttempts,
   231  		MaxFlakeAttempts:            report.MaxFlakeAttempts,
   232  		MaxMustPassRepeatedly:       report.MaxMustPassRepeatedly,
   233  		CapturedGinkgoWriterOutput:  report.CapturedGinkgoWriterOutput,
   234  		CapturedStdOutErr:           report.CapturedStdOutErr,
   235  	}
   236  
   237  	if !report.Failure.IsZero() {
   238  		out.Failure = &(report.Failure)
   239  	}
   240  	if len(report.ReportEntries) > 0 {
   241  		out.ReportEntries = report.ReportEntries
   242  	}
   243  	if len(report.ProgressReports) > 0 {
   244  		out.ProgressReports = report.ProgressReports
   245  	}
   246  	if len(report.AdditionalFailures) > 0 {
   247  		out.AdditionalFailures = report.AdditionalFailures
   248  	}
   249  	if len(report.SpecEvents) > 0 {
   250  		out.SpecEvents = report.SpecEvents
   251  	}
   252  
   253  	return json.Marshal(out)
   254  }
   255  
   256  // CombinedOutput returns a single string representation of both CapturedStdOutErr and CapturedGinkgoWriterOutput
   257  // Note that both are empty when using CurrentSpecReport() so CurrentSpecReport().CombinedOutput() will always be empty.
   258  // CombinedOutput() is used internally by Ginkgo's reporter.
   259  func (report SpecReport) CombinedOutput() string {
   260  	if report.CapturedStdOutErr == "" {
   261  		return report.CapturedGinkgoWriterOutput
   262  	}
   263  	if report.CapturedGinkgoWriterOutput == "" {
   264  		return report.CapturedStdOutErr
   265  	}
   266  	return report.CapturedStdOutErr + "\n" + report.CapturedGinkgoWriterOutput
   267  }
   268  
   269  // Failed returns true if report.State is one of the SpecStateFailureStates
   270  // (SpecStateFailed, SpecStatePanicked, SpecStateinterrupted, SpecStateAborted)
   271  func (report SpecReport) Failed() bool {
   272  	return report.State.Is(SpecStateFailureStates)
   273  }
   274  
   275  // FullText returns a concatenation of all the report.ContainerHierarchyTexts and report.LeafNodeText
   276  func (report SpecReport) FullText() string {
   277  	texts := []string{}
   278  	texts = append(texts, report.ContainerHierarchyTexts...)
   279  	if report.LeafNodeText != "" {
   280  		texts = append(texts, report.LeafNodeText)
   281  	}
   282  	return strings.Join(texts, " ")
   283  }
   284  
   285  // Labels returns a deduped set of all the spec's Labels.
   286  func (report SpecReport) Labels() []string {
   287  	out := []string{}
   288  	seen := map[string]bool{}
   289  	for _, labels := range report.ContainerHierarchyLabels {
   290  		for _, label := range labels {
   291  			if !seen[label] {
   292  				seen[label] = true
   293  				out = append(out, label)
   294  			}
   295  		}
   296  	}
   297  	for _, label := range report.LeafNodeLabels {
   298  		if !seen[label] {
   299  			seen[label] = true
   300  			out = append(out, label)
   301  		}
   302  	}
   303  
   304  	return out
   305  }
   306  
   307  // MatchesLabelFilter returns true if the spec satisfies the passed in label filter query
   308  func (report SpecReport) MatchesLabelFilter(query string) (bool, error) {
   309  	filter, err := ParseLabelFilter(query)
   310  	if err != nil {
   311  		return false, err
   312  	}
   313  	return filter(report.Labels()), nil
   314  }
   315  
   316  // FileName() returns the name of the file containing the spec
   317  func (report SpecReport) FileName() string {
   318  	return report.LeafNodeLocation.FileName
   319  }
   320  
   321  // LineNumber() returns the line number of the leaf node
   322  func (report SpecReport) LineNumber() int {
   323  	return report.LeafNodeLocation.LineNumber
   324  }
   325  
   326  // FailureMessage() returns the failure message (or empty string if the test hasn't failed)
   327  func (report SpecReport) FailureMessage() string {
   328  	return report.Failure.Message
   329  }
   330  
   331  // FailureLocation() returns the location of the failure (or an empty CodeLocation if the test hasn't failed)
   332  func (report SpecReport) FailureLocation() CodeLocation {
   333  	return report.Failure.Location
   334  }
   335  
   336  // Timeline() returns a timeline view of the report
   337  func (report SpecReport) Timeline() Timeline {
   338  	timeline := Timeline{}
   339  	if !report.Failure.IsZero() {
   340  		timeline = append(timeline, report.Failure)
   341  		if report.Failure.AdditionalFailure != nil {
   342  			timeline = append(timeline, *(report.Failure.AdditionalFailure))
   343  		}
   344  	}
   345  	for _, additionalFailure := range report.AdditionalFailures {
   346  		timeline = append(timeline, additionalFailure)
   347  	}
   348  	for _, reportEntry := range report.ReportEntries {
   349  		timeline = append(timeline, reportEntry)
   350  	}
   351  	for _, progressReport := range report.ProgressReports {
   352  		timeline = append(timeline, progressReport)
   353  	}
   354  	for _, specEvent := range report.SpecEvents {
   355  		timeline = append(timeline, specEvent)
   356  	}
   357  	sort.Sort(timeline)
   358  	return timeline
   359  }
   360  
   361  type SpecReports []SpecReport
   362  
   363  // WithLeafNodeType returns the subset of SpecReports with LeafNodeType matching one of the requested NodeTypes
   364  func (reports SpecReports) WithLeafNodeType(nodeTypes NodeType) SpecReports {
   365  	count := 0
   366  	for i := range reports {
   367  		if reports[i].LeafNodeType.Is(nodeTypes) {
   368  			count++
   369  		}
   370  	}
   371  
   372  	out := make(SpecReports, count)
   373  	j := 0
   374  	for i := range reports {
   375  		if reports[i].LeafNodeType.Is(nodeTypes) {
   376  			out[j] = reports[i]
   377  			j++
   378  		}
   379  	}
   380  	return out
   381  }
   382  
   383  // WithState returns the subset of SpecReports with State matching one of the requested SpecStates
   384  func (reports SpecReports) WithState(states SpecState) SpecReports {
   385  	count := 0
   386  	for i := range reports {
   387  		if reports[i].State.Is(states) {
   388  			count++
   389  		}
   390  	}
   391  
   392  	out, j := make(SpecReports, count), 0
   393  	for i := range reports {
   394  		if reports[i].State.Is(states) {
   395  			out[j] = reports[i]
   396  			j++
   397  		}
   398  	}
   399  	return out
   400  }
   401  
   402  // CountWithState returns the number of SpecReports with State matching one of the requested SpecStates
   403  func (reports SpecReports) CountWithState(states SpecState) int {
   404  	n := 0
   405  	for i := range reports {
   406  		if reports[i].State.Is(states) {
   407  			n += 1
   408  		}
   409  	}
   410  	return n
   411  }
   412  
   413  // If the Spec passes, CountOfFlakedSpecs returns the number of SpecReports that failed after multiple attempts.
   414  func (reports SpecReports) CountOfFlakedSpecs() int {
   415  	n := 0
   416  	for i := range reports {
   417  		if reports[i].MaxFlakeAttempts > 1 && reports[i].State.Is(SpecStatePassed) && reports[i].NumAttempts > 1 {
   418  			n += 1
   419  		}
   420  	}
   421  	return n
   422  }
   423  
   424  // If the Spec fails, CountOfRepeatedSpecs returns the number of SpecReports that passed after multiple attempts
   425  func (reports SpecReports) CountOfRepeatedSpecs() int {
   426  	n := 0
   427  	for i := range reports {
   428  		if reports[i].MaxMustPassRepeatedly > 1 && reports[i].State.Is(SpecStateFailureStates) && reports[i].NumAttempts > 1 {
   429  			n += 1
   430  		}
   431  	}
   432  	return n
   433  }
   434  
   435  // TimelineLocation captures the location of an event in the spec's timeline
   436  type TimelineLocation struct {
   437  	//Offset is the offset (in bytes) of the event relative to the GinkgoWriter stream
   438  	Offset int `json:",omitempty"`
   439  
   440  	//Order is the order of the event with respect to other events.  The absolute value of Order
   441  	//is irrelevant.  All that matters is that an event with a lower Order occurs before ane vent with a higher Order
   442  	Order int `json:",omitempty"`
   443  
   444  	Time time.Time
   445  }
   446  
   447  // TimelineEvent represent an event on the timeline
   448  // consumers of Timeline will need to check the concrete type of each entry to determine how to handle it
   449  type TimelineEvent interface {
   450  	GetTimelineLocation() TimelineLocation
   451  }
   452  
   453  type Timeline []TimelineEvent
   454  
   455  func (t Timeline) Len() int { return len(t) }
   456  func (t Timeline) Less(i, j int) bool {
   457  	return t[i].GetTimelineLocation().Order < t[j].GetTimelineLocation().Order
   458  }
   459  func (t Timeline) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
   460  func (t Timeline) WithoutHiddenReportEntries() Timeline {
   461  	out := Timeline{}
   462  	for _, event := range t {
   463  		if reportEntry, isReportEntry := event.(ReportEntry); isReportEntry && reportEntry.Visibility == ReportEntryVisibilityNever {
   464  			continue
   465  		}
   466  		out = append(out, event)
   467  	}
   468  	return out
   469  }
   470  
   471  func (t Timeline) WithoutVeryVerboseSpecEvents() Timeline {
   472  	out := Timeline{}
   473  	for _, event := range t {
   474  		if specEvent, isSpecEvent := event.(SpecEvent); isSpecEvent && specEvent.IsOnlyVisibleAtVeryVerbose() {
   475  			continue
   476  		}
   477  		out = append(out, event)
   478  	}
   479  	return out
   480  }
   481  
   482  // Failure captures failure information for an individual test
   483  type Failure struct {
   484  	// Message - the failure message passed into Fail(...).  When using a matcher library
   485  	// like Gomega, this will contain the failure message generated by Gomega.
   486  	//
   487  	// Message is also populated if the user has called Skip(...).
   488  	Message string
   489  
   490  	// Location - the CodeLocation where the failure occurred
   491  	// This CodeLocation will include a fully-populated StackTrace
   492  	Location CodeLocation
   493  
   494  	TimelineLocation TimelineLocation
   495  
   496  	// ForwardedPanic - if the failure represents a captured panic (i.e. Summary.State == SpecStatePanicked)
   497  	// then ForwardedPanic will be populated with a string representation of the captured panic.
   498  	ForwardedPanic string `json:",omitempty"`
   499  
   500  	// FailureNodeContext - one of three contexts describing the node in which the failure occurred:
   501  	// FailureNodeIsLeafNode means the failure occurred in the leaf node of the associated SpecReport. None of the other FailureNode fields will be populated
   502  	// FailureNodeAtTopLevel means the failure occurred in a non-leaf node that is defined at the top-level of the spec (i.e. not in a container). FailureNodeType and FailureNodeLocation will be populated.
   503  	// FailureNodeInContainer means the failure occurred in a non-leaf node that is defined within a container.  FailureNodeType, FailureNodeLocation, and FailureNodeContainerIndex will be populated.
   504  	//
   505  	// FailureNodeType will contain the NodeType of the node in which the failure occurred.
   506  	// FailureNodeLocation will contain the CodeLocation of the node in which the failure occurred.
   507  	// If populated, FailureNodeContainerIndex will be the index into SpecReport.ContainerHierarchyTexts and SpecReport.ContainerHierarchyLocations that represents the parent container of the node in which the failure occurred.
   508  	FailureNodeContext FailureNodeContext `json:",omitempty"`
   509  
   510  	FailureNodeType NodeType `json:",omitempty"`
   511  
   512  	FailureNodeLocation CodeLocation `json:",omitempty"`
   513  
   514  	FailureNodeContainerIndex int `json:",omitempty"`
   515  
   516  	//ProgressReport is populated if the spec was interrupted or timed out
   517  	ProgressReport ProgressReport `json:",omitempty"`
   518  
   519  	//AdditionalFailure is non-nil if a follow-on failure occurred within the same node after the primary failure.  This only happens when a node has timed out or been interrupted.  In such cases the AdditionalFailure can include information about where/why the spec was stuck.
   520  	AdditionalFailure *AdditionalFailure `json:",omitempty"`
   521  }
   522  
   523  func (f Failure) IsZero() bool {
   524  	return f.Message == "" && (f.Location == CodeLocation{})
   525  }
   526  
   527  func (f Failure) GetTimelineLocation() TimelineLocation {
   528  	return f.TimelineLocation
   529  }
   530  
   531  // FailureNodeContext captures the location context for the node containing the failing line of code
   532  type FailureNodeContext uint
   533  
   534  const (
   535  	FailureNodeContextInvalid FailureNodeContext = iota
   536  
   537  	FailureNodeIsLeafNode
   538  	FailureNodeAtTopLevel
   539  	FailureNodeInContainer
   540  )
   541  
   542  var fncEnumSupport = NewEnumSupport(map[uint]string{
   543  	uint(FailureNodeContextInvalid): "INVALID FAILURE NODE CONTEXT",
   544  	uint(FailureNodeIsLeafNode):     "leaf-node",
   545  	uint(FailureNodeAtTopLevel):     "top-level",
   546  	uint(FailureNodeInContainer):    "in-container",
   547  })
   548  
   549  func (fnc FailureNodeContext) String() string {
   550  	return fncEnumSupport.String(uint(fnc))
   551  }
   552  func (fnc *FailureNodeContext) UnmarshalJSON(b []byte) error {
   553  	out, err := fncEnumSupport.UnmarshJSON(b)
   554  	*fnc = FailureNodeContext(out)
   555  	return err
   556  }
   557  func (fnc FailureNodeContext) MarshalJSON() ([]byte, error) {
   558  	return fncEnumSupport.MarshJSON(uint(fnc))
   559  }
   560  
   561  // AdditionalFailure capturs any additional failures that occur after the initial failure of a psec
   562  // these typically occur in clean up nodes after the spec has failed.
   563  // We can't simply use Failure as we want to track the SpecState to know what kind of failure this is
   564  type AdditionalFailure struct {
   565  	State   SpecState
   566  	Failure Failure
   567  }
   568  
   569  func (f AdditionalFailure) GetTimelineLocation() TimelineLocation {
   570  	return f.Failure.TimelineLocation
   571  }
   572  
   573  // SpecState captures the state of a spec
   574  // To determine if a given `state` represents a failure state, use `state.Is(SpecStateFailureStates)`
   575  type SpecState uint
   576  
   577  const (
   578  	SpecStateInvalid SpecState = 0
   579  
   580  	SpecStatePending SpecState = 1 << iota
   581  	SpecStateSkipped
   582  	SpecStatePassed
   583  	SpecStateFailed
   584  	SpecStateAborted
   585  	SpecStatePanicked
   586  	SpecStateInterrupted
   587  	SpecStateTimedout
   588  )
   589  
   590  var ssEnumSupport = NewEnumSupport(map[uint]string{
   591  	uint(SpecStateInvalid):     "INVALID SPEC STATE",
   592  	uint(SpecStatePending):     "pending",
   593  	uint(SpecStateSkipped):     "skipped",
   594  	uint(SpecStatePassed):      "passed",
   595  	uint(SpecStateFailed):      "failed",
   596  	uint(SpecStateAborted):     "aborted",
   597  	uint(SpecStatePanicked):    "panicked",
   598  	uint(SpecStateInterrupted): "interrupted",
   599  	uint(SpecStateTimedout):    "timedout",
   600  })
   601  
   602  func (ss SpecState) String() string {
   603  	return ssEnumSupport.String(uint(ss))
   604  }
   605  func (ss SpecState) GomegaString() string {
   606  	return ssEnumSupport.String(uint(ss))
   607  }
   608  func (ss *SpecState) UnmarshalJSON(b []byte) error {
   609  	out, err := ssEnumSupport.UnmarshJSON(b)
   610  	*ss = SpecState(out)
   611  	return err
   612  }
   613  func (ss SpecState) MarshalJSON() ([]byte, error) {
   614  	return ssEnumSupport.MarshJSON(uint(ss))
   615  }
   616  
   617  var SpecStateFailureStates = SpecStateFailed | SpecStateTimedout | SpecStateAborted | SpecStatePanicked | SpecStateInterrupted
   618  
   619  func (ss SpecState) Is(states SpecState) bool {
   620  	return ss&states != 0
   621  }
   622  
   623  // ProgressReport captures the progress of the current spec.  It is, effectively, a structured Ginkgo-aware stack trace
   624  type ProgressReport struct {
   625  	Message           string `json:",omitempty"`
   626  	ParallelProcess   int    `json:",omitempty"`
   627  	RunningInParallel bool   `json:",omitempty"`
   628  
   629  	ContainerHierarchyTexts []string     `json:",omitempty"`
   630  	LeafNodeText            string       `json:",omitempty"`
   631  	LeafNodeLocation        CodeLocation `json:",omitempty"`
   632  	SpecStartTime           time.Time    `json:",omitempty"`
   633  
   634  	CurrentNodeType      NodeType     `json:",omitempty"`
   635  	CurrentNodeText      string       `json:",omitempty"`
   636  	CurrentNodeLocation  CodeLocation `json:",omitempty"`
   637  	CurrentNodeStartTime time.Time    `json:",omitempty"`
   638  
   639  	CurrentStepText      string       `json:",omitempty"`
   640  	CurrentStepLocation  CodeLocation `json:",omitempty"`
   641  	CurrentStepStartTime time.Time    `json:",omitempty"`
   642  
   643  	AdditionalReports []string `json:",omitempty"`
   644  
   645  	CapturedGinkgoWriterOutput string           `json:",omitempty"`
   646  	TimelineLocation           TimelineLocation `json:",omitempty"`
   647  
   648  	Goroutines []Goroutine `json:",omitempty"`
   649  }
   650  
   651  func (pr ProgressReport) IsZero() bool {
   652  	return pr.CurrentNodeType == NodeTypeInvalid
   653  }
   654  
   655  func (pr ProgressReport) Time() time.Time {
   656  	return pr.TimelineLocation.Time
   657  }
   658  
   659  func (pr ProgressReport) SpecGoroutine() Goroutine {
   660  	for _, goroutine := range pr.Goroutines {
   661  		if goroutine.IsSpecGoroutine {
   662  			return goroutine
   663  		}
   664  	}
   665  	return Goroutine{}
   666  }
   667  
   668  func (pr ProgressReport) HighlightedGoroutines() []Goroutine {
   669  	out := []Goroutine{}
   670  	for _, goroutine := range pr.Goroutines {
   671  		if goroutine.IsSpecGoroutine || !goroutine.HasHighlights() {
   672  			continue
   673  		}
   674  		out = append(out, goroutine)
   675  	}
   676  	return out
   677  }
   678  
   679  func (pr ProgressReport) OtherGoroutines() []Goroutine {
   680  	out := []Goroutine{}
   681  	for _, goroutine := range pr.Goroutines {
   682  		if goroutine.IsSpecGoroutine || goroutine.HasHighlights() {
   683  			continue
   684  		}
   685  		out = append(out, goroutine)
   686  	}
   687  	return out
   688  }
   689  
   690  func (pr ProgressReport) WithoutCapturedGinkgoWriterOutput() ProgressReport {
   691  	out := pr
   692  	out.CapturedGinkgoWriterOutput = ""
   693  	return out
   694  }
   695  
   696  func (pr ProgressReport) WithoutOtherGoroutines() ProgressReport {
   697  	out := pr
   698  	filteredGoroutines := []Goroutine{}
   699  	for _, goroutine := range pr.Goroutines {
   700  		if goroutine.IsSpecGoroutine || goroutine.HasHighlights() {
   701  			filteredGoroutines = append(filteredGoroutines, goroutine)
   702  		}
   703  	}
   704  	out.Goroutines = filteredGoroutines
   705  	return out
   706  }
   707  
   708  func (pr ProgressReport) GetTimelineLocation() TimelineLocation {
   709  	return pr.TimelineLocation
   710  }
   711  
   712  type Goroutine struct {
   713  	ID              uint64
   714  	State           string
   715  	Stack           []FunctionCall
   716  	IsSpecGoroutine bool
   717  }
   718  
   719  func (g Goroutine) IsZero() bool {
   720  	return g.ID == 0
   721  }
   722  
   723  func (g Goroutine) HasHighlights() bool {
   724  	for _, fc := range g.Stack {
   725  		if fc.Highlight {
   726  			return true
   727  		}
   728  	}
   729  
   730  	return false
   731  }
   732  
   733  type FunctionCall struct {
   734  	Function        string
   735  	Filename        string
   736  	Line            int
   737  	Highlight       bool     `json:",omitempty"`
   738  	Source          []string `json:",omitempty"`
   739  	SourceHighlight int      `json:",omitempty"`
   740  }
   741  
   742  // NodeType captures the type of a given Ginkgo Node
   743  type NodeType uint
   744  
   745  const (
   746  	NodeTypeInvalid NodeType = 0
   747  
   748  	NodeTypeContainer NodeType = 1 << iota
   749  	NodeTypeIt
   750  
   751  	NodeTypeBeforeEach
   752  	NodeTypeJustBeforeEach
   753  	NodeTypeAfterEach
   754  	NodeTypeJustAfterEach
   755  
   756  	NodeTypeBeforeAll
   757  	NodeTypeAfterAll
   758  
   759  	NodeTypeBeforeSuite
   760  	NodeTypeSynchronizedBeforeSuite
   761  	NodeTypeAfterSuite
   762  	NodeTypeSynchronizedAfterSuite
   763  
   764  	NodeTypeReportBeforeEach
   765  	NodeTypeReportAfterEach
   766  	NodeTypeReportBeforeSuite
   767  	NodeTypeReportAfterSuite
   768  
   769  	NodeTypeCleanupInvalid
   770  	NodeTypeCleanupAfterEach
   771  	NodeTypeCleanupAfterAll
   772  	NodeTypeCleanupAfterSuite
   773  )
   774  
   775  var NodeTypesForContainerAndIt = NodeTypeContainer | NodeTypeIt
   776  var NodeTypesForSuiteLevelNodes = NodeTypeBeforeSuite | NodeTypeSynchronizedBeforeSuite | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite | NodeTypeCleanupAfterSuite
   777  var NodeTypesAllowedDuringCleanupInterrupt = NodeTypeAfterEach | NodeTypeJustAfterEach | NodeTypeAfterAll | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeCleanupAfterEach | NodeTypeCleanupAfterAll | NodeTypeCleanupAfterSuite
   778  var NodeTypesAllowedDuringReportInterrupt = NodeTypeReportBeforeEach | NodeTypeReportAfterEach | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite
   779  
   780  var ntEnumSupport = NewEnumSupport(map[uint]string{
   781  	uint(NodeTypeInvalid):                 "INVALID NODE TYPE",
   782  	uint(NodeTypeContainer):               "Container",
   783  	uint(NodeTypeIt):                      "It",
   784  	uint(NodeTypeBeforeEach):              "BeforeEach",
   785  	uint(NodeTypeJustBeforeEach):          "JustBeforeEach",
   786  	uint(NodeTypeAfterEach):               "AfterEach",
   787  	uint(NodeTypeJustAfterEach):           "JustAfterEach",
   788  	uint(NodeTypeBeforeAll):               "BeforeAll",
   789  	uint(NodeTypeAfterAll):                "AfterAll",
   790  	uint(NodeTypeBeforeSuite):             "BeforeSuite",
   791  	uint(NodeTypeSynchronizedBeforeSuite): "SynchronizedBeforeSuite",
   792  	uint(NodeTypeAfterSuite):              "AfterSuite",
   793  	uint(NodeTypeSynchronizedAfterSuite):  "SynchronizedAfterSuite",
   794  	uint(NodeTypeReportBeforeEach):        "ReportBeforeEach",
   795  	uint(NodeTypeReportAfterEach):         "ReportAfterEach",
   796  	uint(NodeTypeReportBeforeSuite):       "ReportBeforeSuite",
   797  	uint(NodeTypeReportAfterSuite):        "ReportAfterSuite",
   798  	uint(NodeTypeCleanupInvalid):          "DeferCleanup",
   799  	uint(NodeTypeCleanupAfterEach):        "DeferCleanup (Each)",
   800  	uint(NodeTypeCleanupAfterAll):         "DeferCleanup (All)",
   801  	uint(NodeTypeCleanupAfterSuite):       "DeferCleanup (Suite)",
   802  })
   803  
   804  func (nt NodeType) String() string {
   805  	return ntEnumSupport.String(uint(nt))
   806  }
   807  func (nt *NodeType) UnmarshalJSON(b []byte) error {
   808  	out, err := ntEnumSupport.UnmarshJSON(b)
   809  	*nt = NodeType(out)
   810  	return err
   811  }
   812  func (nt NodeType) MarshalJSON() ([]byte, error) {
   813  	return ntEnumSupport.MarshJSON(uint(nt))
   814  }
   815  
   816  func (nt NodeType) Is(nodeTypes NodeType) bool {
   817  	return nt&nodeTypes != 0
   818  }
   819  
   820  /*
   821  SpecEvent captures a vareity of events that can occur when specs run.  See SpecEventType for the list of available events.
   822  */
   823  type SpecEvent struct {
   824  	SpecEventType SpecEventType
   825  
   826  	CodeLocation     CodeLocation
   827  	TimelineLocation TimelineLocation
   828  
   829  	Message  string        `json:",omitempty"`
   830  	Duration time.Duration `json:",omitempty"`
   831  	NodeType NodeType      `json:",omitempty"`
   832  	Attempt  int           `json:",omitempty"`
   833  }
   834  
   835  func (se SpecEvent) GetTimelineLocation() TimelineLocation {
   836  	return se.TimelineLocation
   837  }
   838  
   839  func (se SpecEvent) IsOnlyVisibleAtVeryVerbose() bool {
   840  	return se.SpecEventType.Is(SpecEventByEnd | SpecEventNodeStart | SpecEventNodeEnd)
   841  }
   842  
   843  func (se SpecEvent) GomegaString() string {
   844  	out := &strings.Builder{}
   845  	out.WriteString("[" + se.SpecEventType.String() + " SpecEvent] ")
   846  	if se.Message != "" {
   847  		out.WriteString("Message=")
   848  		out.WriteString(`"` + se.Message + `",`)
   849  	}
   850  	if se.Duration != 0 {
   851  		out.WriteString("Duration=" + se.Duration.String() + ",")
   852  	}
   853  	if se.NodeType != NodeTypeInvalid {
   854  		out.WriteString("NodeType=" + se.NodeType.String() + ",")
   855  	}
   856  	if se.Attempt != 0 {
   857  		out.WriteString(fmt.Sprintf("Attempt=%d", se.Attempt) + ",")
   858  	}
   859  	out.WriteString("CL=" + se.CodeLocation.String() + ",")
   860  	out.WriteString(fmt.Sprintf("TL.Offset=%d", se.TimelineLocation.Offset))
   861  
   862  	return out.String()
   863  }
   864  
   865  type SpecEvents []SpecEvent
   866  
   867  func (se SpecEvents) WithType(seType SpecEventType) SpecEvents {
   868  	out := SpecEvents{}
   869  	for _, event := range se {
   870  		if event.SpecEventType.Is(seType) {
   871  			out = append(out, event)
   872  		}
   873  	}
   874  	return out
   875  }
   876  
   877  type SpecEventType uint
   878  
   879  const (
   880  	SpecEventInvalid SpecEventType = 0
   881  
   882  	SpecEventByStart SpecEventType = 1 << iota
   883  	SpecEventByEnd
   884  	SpecEventNodeStart
   885  	SpecEventNodeEnd
   886  	SpecEventSpecRepeat
   887  	SpecEventSpecRetry
   888  )
   889  
   890  var seEnumSupport = NewEnumSupport(map[uint]string{
   891  	uint(SpecEventInvalid):    "INVALID SPEC EVENT",
   892  	uint(SpecEventByStart):    "By",
   893  	uint(SpecEventByEnd):      "By (End)",
   894  	uint(SpecEventNodeStart):  "Node",
   895  	uint(SpecEventNodeEnd):    "Node (End)",
   896  	uint(SpecEventSpecRepeat): "Repeat",
   897  	uint(SpecEventSpecRetry):  "Retry",
   898  })
   899  
   900  func (se SpecEventType) String() string {
   901  	return seEnumSupport.String(uint(se))
   902  }
   903  func (se *SpecEventType) UnmarshalJSON(b []byte) error {
   904  	out, err := seEnumSupport.UnmarshJSON(b)
   905  	*se = SpecEventType(out)
   906  	return err
   907  }
   908  func (se SpecEventType) MarshalJSON() ([]byte, error) {
   909  	return seEnumSupport.MarshJSON(uint(se))
   910  }
   911  
   912  func (se SpecEventType) Is(specEventTypes SpecEventType) bool {
   913  	return se&specEventTypes != 0
   914  }
   915  

View as plain text