diff --git a/assets/catnip/go.mod b/assets/catnip/go.mod index 4073ac007..f633e1156 100644 --- a/assets/catnip/go.mod +++ b/assets/catnip/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( code.cloudfoundry.org/clock v1.0.1-0.20220601152104-3983b71fbf93 github.com/gorilla/mux v1.8.0 - github.com/onsi/ginkgo/v2 v2.1.6 + github.com/onsi/ginkgo/v2 v2.2.0 github.com/onsi/gomega v1.20.2 ) diff --git a/assets/catnip/go.sum b/assets/catnip/go.sum index b807fc528..6f520df6b 100644 --- a/assets/catnip/go.sum +++ b/assets/catnip/go.sum @@ -41,8 +41,8 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108 github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU= -github.com/onsi/ginkgo/v2 v2.1.6 h1:Fx2POJZfKRQcM1pH49qSZiYeu319wji004qX+GDovrU= -github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= +github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= +github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md index cd43074d7..f79662bf5 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md @@ -1,3 +1,27 @@ +## 2.2.0 + +### Generate real-time Progress Reports [f91377c] + +Ginkgo can now generate Progress Reports to point users at the current running line of code (including a preview of the actual source code) and a best guess at the most relevant subroutines. + +These Progress Reports allow users to debug stuck or slow tests without exiting the Ginkgo process. A Progress Report can be generated at any time by sending Ginkgo a `SIGINFO` (`^T` on MacOS/BSD) or `SIGUSR1`. + +In addition, the user can specify `--poll-progress-after` and `--poll-progress-interval` to have Ginkgo start periodically emitting progress reports if a given node takes too long. These can be overriden/set on a per-node basis with the `PollProgressAfter` and `PollProgressInterval` decorators. + +Progress Reports are emitted to stdout, and also stored in the machine-redable report formats that Ginkgo supports. + +Ginkgo also uses this progress reporting infrastructure under the hood when handling timeouts and interrupts. This yields much more focused, useful, and informative stack traces than previously. + +### Features +- `BeforeSuite`, `AfterSuite`, `SynchronizedBeforeSuite`, `SynchronizedAfterSuite`, and `ReportAfterSuite` now support (the relevant subset of) decorators. These can be passed in _after_ the callback functions that are usually passed into these nodes. + + As a result the **signature of these methods has changed** and now includes a trailing `args ...interface{}`. For most users simply using the DSL, this change is transparent. However if you were assigning one of these functions to a custom variable (or passing it around) then your code may need to change to reflect the new signature. + +### Maintenance +- Modernize the invocation of Ginkgo in github actions [0ffde58] +- Update reocmmended CI settings in docs [896bbb9] +- Speed up unnecessarily slow integration test [6d3a90e] + ## 2.1.6 ### Fixes @@ -77,7 +101,7 @@ See [https://onsi.github.io/ginkgo/MIGRATING_TO_V2](https://onsi.github.io/ginkg Ginkgo 2.0 now has a Release Candidate. 1.16.5 advertises the existence of the RC. 1.16.5 deprecates GinkgoParallelNode in favor of GinkgoParallelProcess -You can silence the RC advertisement by setting an `ACK_GINKG_RC=true` environment variable or creating a file in your home directory called `.ack-ginkgo-rc` +You can silence the RC advertisement by setting an `ACK_GINKGO_RC=true` environment variable or creating a file in your home directory called `.ack-ginkgo-rc` ## 1.16.4 @@ -184,7 +208,7 @@ You can silence the RC advertisement by setting an `ACK_GINKG_RC=true` environme - replace tail package with maintained one. this fixes go get errors (#667) [4ba33d4] - improve ginkgo performance - makes progress on #644 [a14f98e] - fix convert integration tests [1f8ba69] -- fix typo succesful -> successful (#663) [1ea49cf] +- fix typo successful -> successful (#663) [1ea49cf] - Fix invalid link (#658) [b886136] - convert utility : Include comments from source (#657) [1077c6d] - Explain what BDD means [d79e7fb] @@ -278,7 +302,7 @@ You can silence the RC advertisement by setting an `ACK_GINKG_RC=true` environme - Make generated Junit file compatible with "Maven Surefire" (#488) [e51bee6] - all: gofmt [000d317] - Increase eventually timeout to 30s [c73579c] -- Clarify asynchronous test behaviour [294d8f4] +- Clarify asynchronous test behavior [294d8f4] - Travis badge should only show master [26d2143] ## 1.5.0 5/10/2018 @@ -296,13 +320,13 @@ You can silence the RC advertisement by setting an `ACK_GINKG_RC=true` environme - When running a test and calculating the coverage using the `-coverprofile` and `-outputdir` flags, Ginkgo fails with an error if the directory does not exist. This is due to an [issue in go 1.10](https://github.com/golang/go/issues/24588) (#446) [b36a6e0] - `unfocus` command ignores vendor folder (#459) [e5e551c, c556e43, a3b6351, 9a820dd] - Ignore packages whose tests are all ignored by go (#456) [7430ca7, 6d8be98] -- Increase the threshold when checking time measuments (#455) [2f714bf, 68f622c] +- Increase the threshold when checking time measurements (#455) [2f714bf, 68f622c] - Fix race condition in coverage tests (#423) [a5a8ff7, ab9c08b] - Add an extra new line after reporting spec run completion for test2json [874520d] - added name name field to junit reported testsuite [ae61c63] - Do not set the run time of a spec when the dryRun flag is used (#438) [457e2d9, ba8e856] - Process FWhen and FSpecify when unfocusing (#434) [9008c7b, ee65bd, df87dfe] -- Synchronise the access to the state of specs to avoid race conditions (#430) [7d481bc, ae6829d] +- Synchronies the access to the state of specs to avoid race conditions (#430) [7d481bc, ae6829d] - Added Duration on GinkgoTestDescription (#383) [5f49dad, 528417e, 0747408, 329d7ed] - Fix Ginkgo stack trace on failure for Specify (#415) [b977ede, 65ca40e, 6c46eb8] - Update README with Go 1.6+, Golang -> Go (#409) [17f6b97, bc14b66, 20d1598] diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md index 15079406e..1da92fe7e 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/CONTRIBUTING.md @@ -8,6 +8,6 @@ Your contributions to Ginkgo are essential for its long-term maintenance and imp - When adding to the Ginkgo CLI, note that there are very few unit tests. Please add an integration test. - Make sure all the tests succeed via `ginkgo -r -p` - Vet your changes via `go vet ./...` -- Update the documentation. Ginko uses `godoc` comments and documentation in `docs/index.md`. You can run `bundle exec jekyll serve` in the `docs` directory to preview your changes. +- Update the documentation. Ginkgo uses `godoc` comments and documentation in `docs/index.md`. You can run `bundle exec jekyll serve` in the `docs` directory to preview your changes. Thanks for supporting Ginkgo! \ No newline at end of file diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/core_dsl.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/core_dsl.go index aaede5c5f..ff8d2790b 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/core_dsl.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/core_dsl.go @@ -277,7 +277,7 @@ func RunSpecs(t GinkgoTestingT, description string, args ...interface{}) bool { suitePath, err = filepath.Abs(suitePath) exitIfErr(err) - passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(suiteConfig.Timeout, client), client, suiteConfig) + passed, hasFocusedTests := global.Suite.Run(description, suiteLabels, suitePath, global.Failer, reporter, writer, outputInterceptor, interrupt_handler.NewInterruptHandler(suiteConfig.Timeout, client), client, internal.RegisterForProgressSignal, suiteConfig) outputInterceptor.Shutdown() flagSet.ValidateDeprecations(deprecationTracker) @@ -504,6 +504,11 @@ func By(text string, callback ...func()) { Text: text, } t := time.Now() + global.Suite.SetProgressStepCursor(internal.ProgressStepCursor{ + Text: text, + CodeLocation: types.NewCodeLocation(1), + StartTime: t, + }) AddReportEntry("By Step", ReportEntryVisibilityNever, Offset(1), &value, t) formatter := formatter.NewWithNoColorBool(reporterConfig.NoColor) GinkgoWriter.Println(formatter.F("{{bold}}STEP:{{/}} %s {{gray}}%s{{/}}", text, t.Format(types.GINKGO_TIME_FORMAT))) @@ -525,8 +530,10 @@ You may only register *one* BeforeSuite handler per test suite. You typically d You cannot nest any other Ginkgo nodes within a BeforeSuite node's closure. You can learn more here: https://onsi.github.io/ginkgo/#suite-setup-and-cleanup-beforesuite-and-aftersuite */ -func BeforeSuite(body func()) bool { - return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeSuite, "", body)) +func BeforeSuite(body func(), args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeBeforeSuite, "", combinedArgs...)) } /* @@ -540,8 +547,10 @@ You may only register *one* AfterSuite handler per test suite. You typically do You cannot nest any other Ginkgo nodes within an AfterSuite node's closure. You can learn more here: https://onsi.github.io/ginkgo/#suite-setup-and-cleanup-beforesuite-and-aftersuite */ -func AfterSuite(body func()) bool { - return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterSuite, "", body)) +func AfterSuite(body func(), args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeAfterSuite, "", combinedArgs...)) } /* @@ -563,8 +572,11 @@ The byte array returned by the first function is then passed to the second funct You cannot nest any other Ginkgo nodes within an SynchronizedBeforeSuite node's closure. You can learn more, and see some examples, here: https://onsi.github.io/ginkgo/#parallel-suite-setup-and-cleanup-synchronizedbeforesuite-and-synchronizedaftersuite */ -func SynchronizedBeforeSuite(process1Body func() []byte, allProcessBody func([]byte)) bool { - return pushNode(internal.NewSynchronizedBeforeSuiteNode(process1Body, allProcessBody, types.NewCodeLocation(1))) +func SynchronizedBeforeSuite(process1Body func() []byte, allProcessBody func([]byte), args ...interface{}) bool { + combinedArgs := []interface{}{process1Body, allProcessBody} + combinedArgs = append(combinedArgs, args...) + + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeSynchronizedBeforeSuite, "", combinedArgs...)) } /* @@ -580,8 +592,11 @@ Note that you can also use DeferCleanup() in SynchronizedBeforeSuite to accompli You cannot nest any other Ginkgo nodes within an SynchronizedAfterSuite node's closure. You can learn more, and see some examples, here: https://onsi.github.io/ginkgo/#parallel-suite-setup-and-cleanup-synchronizedbeforesuite-and-synchronizedaftersuite */ -func SynchronizedAfterSuite(allProcessBody func(), process1Body func()) bool { - return pushNode(internal.NewSynchronizedAfterSuiteNode(allProcessBody, process1Body, types.NewCodeLocation(1))) +func SynchronizedAfterSuite(allProcessBody func(), process1Body func(), args ...interface{}) bool { + combinedArgs := []interface{}{allProcessBody, process1Body} + combinedArgs = append(combinedArgs, args...) + + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeSynchronizedAfterSuite, "", combinedArgs...)) } /* diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go index 6ef4b7598..a31a2ee6d 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/decorator_dsl.go @@ -59,7 +59,7 @@ OncePerOrdered is a decorator that allows you to mark outer BeforeEach, AfterEac per ordered context. Normally these setup nodes run around each individual spec, with OncePerOrdered they will run once around the set of specs in an ordered container. The behavior for non-Ordered containers/specs is unchanged. -You can learh more here: https://onsi.github.io/ginkgo/#setup-around-ordered-containers-the-onceperordered-decorator +You can learn more here: https://onsi.github.io/ginkgo/#setup-around-ordered-containers-the-onceperordered-decorator You can learn more about decorators here: https://onsi.github.io/ginkgo/#decorator-reference */ const OncePerOrdered = internal.OncePerOrdered @@ -81,6 +81,20 @@ You can learn more here: https://onsi.github.io/ginkgo/#spec-labels */ type Labels = internal.Labels +/* +PollProgressAfter allows you to override the configured value for --poll-progress-after for a particular node. + +Ginkgo will start emitting node progress if the node is still running after a duration of PollProgressAfter. This allows you to get quicker feedback about the state of a long-running spec. +*/ +type PollProgressAfter = internal.PollProgressAfter + +/* +PollProgressInterval allows you to override the configured value for --poll-progress-interval for a particular node. + +Once a node has been running for longer than PollProgressAfter Ginkgo will emit node progress periodically at an interval of PollProgresInterval. +*/ +type PollProgressInterval = internal.PollProgressInterval + /* SuppressProgressReporting is a decorator that allows you to disable progress reporting of a particular node. This is useful if `ginkgo -v -progress` is generating too much noise; particularly if you have a `ReportAfterEach` node that is running for every skipped spec and is generating lots of progress reports. diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go index d20e5a8ce..f912bbec6 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/deprecated_dsl.go @@ -13,7 +13,7 @@ import ( Deprecated: Done Channel for asynchronous testing The Done channel pattern is no longer supported in Ginkgo 2.0. -See here for better patterns for asynchronouse testing: https://onsi.github.io/ginkgo/#patterns-for-asynchronous-testing +See here for better patterns for asynchronous testing: https://onsi.github.io/ginkgo/#patterns-for-asynchronous-testing For a migration guide see: https://onsi.github.io/ginkgo/MIGRATING_TO_V2#removed-async-testing */ diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/group.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/group.go index c6546bba4..ce7e72e82 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/group.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/group.go @@ -279,7 +279,10 @@ func (g *group) run(specs Specs) { } for _, spec := range g.specs { + g.suite.selectiveLock.Lock() g.suite.currentSpecReport = g.initialReportForSpec(spec) + g.suite.selectiveLock.Unlock() + g.suite.currentSpecReport.State, g.suite.currentSpecReport.Failure = g.evaluateSkipStatus(spec) g.suite.reporter.WillRun(g.suite.currentSpecReport) g.suite.reportEach(spec, types.NodeTypeReportBeforeEach) @@ -318,7 +321,9 @@ func (g *group) run(specs Specs) { if g.suite.currentSpecReport.State.Is(types.SpecStateFailureStates) { g.succeeded = false } + g.suite.selectiveLock.Lock() g.suite.currentSpecReport = types.SpecReport{} + g.suite.selectiveLock.Unlock() } } diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go index aca7d1c43..ad224bc59 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/interrupt_handler/interrupt_handler.go @@ -4,12 +4,10 @@ import ( "fmt" "os" "os/signal" - "runtime" "sync" "syscall" "time" - "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/internal/parallel_support" ) @@ -50,7 +48,7 @@ type InterruptHandlerInterface interface { Status() InterruptStatus SetInterruptPlaceholderMessage(string) ClearInterruptPlaceholderMessage() - InterruptMessageWithStackTraces() string + InterruptMessage() (string, bool) } type InterruptHandler struct { @@ -190,23 +188,9 @@ func (handler *InterruptHandler) ClearInterruptPlaceholderMessage() { handler.interruptPlaceholderMessage = "" } -func (handler *InterruptHandler) InterruptMessageWithStackTraces() string { +func (handler *InterruptHandler) InterruptMessage() (string, bool) { handler.lock.Lock() - out := fmt.Sprintf("%s\n\n", handler.interruptCause.String()) + out := fmt.Sprintf("%s", handler.interruptCause.String()) defer handler.lock.Unlock() - if handler.interruptCause == InterruptCauseAbortByOtherProcess { - return out - } - out += "Here's a stack trace of all running goroutines:\n" - buf := make([]byte, 8192) - for { - n := runtime.Stack(buf, true) - if n < len(buf) { - buf = buf[:n] - break - } - buf = make([]byte, 2*len(buf)) - } - out += formatter.Fi(1, "%s", string(buf)) - return out + return out, handler.interruptCause != InterruptCauseAbortByOtherProcess } diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/node.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/node.go index dc11129e6..3dbf0b478 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/node.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/node.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "sort" + "time" "sync" @@ -48,6 +49,8 @@ type Node struct { MarkedSuppressProgressReporting bool FlakeAttempts int Labels Labels + PollProgressAfter time.Duration + PollProgressInterval time.Duration NodeIDWhereCleanupWasGenerated uint } @@ -71,6 +74,8 @@ type FlakeAttempts uint type Offset uint type Done chan<- interface{} // Deprecated Done Channel for asynchronous testing type Labels []string +type PollProgressInterval time.Duration +type PollProgressAfter time.Duration func UnionOfLabels(labels ...Labels) Labels { out := Labels{} @@ -123,6 +128,10 @@ func isDecoration(arg interface{}) bool { return true case t == reflect.TypeOf(Labels{}): return true + case t == reflect.TypeOf(PollProgressInterval(0)): + return true + case t == reflect.TypeOf(PollProgressAfter(0)): + return true case t.Kind() == reflect.Slice && isSliceOfDecorations(arg): return true default: @@ -146,13 +155,16 @@ func isSliceOfDecorations(slice interface{}) bool { func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeType, text string, args ...interface{}) (Node, []error) { baseOffset := 2 node := Node{ - ID: UniqueNodeID(), - NodeType: nodeType, - Text: text, - Labels: Labels{}, - CodeLocation: types.NewCodeLocation(baseOffset), - NestingLevel: -1, + ID: UniqueNodeID(), + NodeType: nodeType, + Text: text, + Labels: Labels{}, + CodeLocation: types.NewCodeLocation(baseOffset), + NestingLevel: -1, + PollProgressAfter: -1, + PollProgressInterval: -1, } + errors := []error{} appendError := func(err error) { if err != nil { @@ -219,6 +231,16 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "FlakeAttempts")) } + case t == reflect.TypeOf(PollProgressAfter(0)): + node.PollProgressAfter = time.Duration(arg.(PollProgressAfter)) + if nodeType.Is(types.NodeTypeContainer) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "PollProgressAfter")) + } + case t == reflect.TypeOf(PollProgressInterval(0)): + node.PollProgressInterval = time.Duration(arg.(PollProgressInterval)) + if nodeType.Is(types.NodeTypeContainer) { + appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "PollProgressInterval")) + } case t == reflect.TypeOf(Labels{}): if !nodeType.Is(types.NodeTypesForContainerAndIt) { appendError(types.GinkgoErrors.InvalidDecoratorForNodeType(node.CodeLocation, nodeType, "Label")) @@ -233,34 +255,60 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy } case t.Kind() == reflect.Func: if nodeType.Is(types.NodeTypeReportBeforeEach | types.NodeTypeReportAfterEach) { - if node.ReportEachBody != nil { + if node.ReportEachBody == nil { + node.ReportEachBody = arg.(func(types.SpecReport)) + } else { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + } else if nodeType.Is(types.NodeTypeSynchronizedBeforeSuite) { + if node.SynchronizedBeforeSuiteProc1Body == nil { + node.SynchronizedBeforeSuiteProc1Body = arg.(func() []byte) + } else if node.SynchronizedBeforeSuiteAllProcsBody == nil { + node.SynchronizedBeforeSuiteAllProcsBody = arg.(func([]byte)) + } else { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + } else if nodeType.Is(types.NodeTypeSynchronizedAfterSuite) { + if node.SynchronizedAfterSuiteAllProcsBody == nil { + node.SynchronizedAfterSuiteAllProcsBody = arg.(func()) + } else if node.SynchronizedAfterSuiteProc1Body == nil { + node.SynchronizedAfterSuiteProc1Body = arg.(func()) + } else { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + } else if nodeType.Is(types.NodeTypeReportAfterSuite) { + if node.ReportAfterSuiteBody == nil { + node.ReportAfterSuiteBody = arg.(func(types.Report)) + } else { appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) trackedFunctionError = true break } - - //we can trust that the function is valid because the compiler has our back here - node.ReportEachBody = arg.(func(types.SpecReport)) - break - } - - if node.Body != nil { - appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) - trackedFunctionError = true - break - } - isValid := (t.NumOut() == 0) && (t.NumIn() <= 1) && (t.NumIn() == 0 || t.In(0) == reflect.TypeOf(make(Done))) - if !isValid { - appendError(types.GinkgoErrors.InvalidBodyType(t, node.CodeLocation, nodeType)) - trackedFunctionError = true - break - } - if t.NumIn() == 0 { - node.Body = arg.(func()) } else { - deprecationTracker.TrackDeprecation(types.Deprecations.Async(), node.CodeLocation) - deprecatedAsyncBody := arg.(func(Done)) - node.Body = func() { deprecatedAsyncBody(make(Done)) } + if node.Body != nil { + appendError(types.GinkgoErrors.MultipleBodyFunctions(node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + isValid := (t.NumOut() == 0) && (t.NumIn() <= 1) && (t.NumIn() == 0 || t.In(0) == reflect.TypeOf(make(Done))) + if !isValid { + appendError(types.GinkgoErrors.InvalidBodyType(t, node.CodeLocation, nodeType)) + trackedFunctionError = true + break + } + if t.NumIn() == 0 { + node.Body = arg.(func()) + } else { + deprecationTracker.TrackDeprecation(types.Deprecations.Async(), node.CodeLocation) + deprecatedAsyncBody := arg.(func(Done)) + node.Body = func() { deprecatedAsyncBody(make(Done)) } + } } default: remainingArgs = append(remainingArgs, arg) @@ -272,7 +320,7 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy appendError(types.GinkgoErrors.InvalidDeclarationOfFocusedAndPending(node.CodeLocation, nodeType)) } - if node.Body == nil && node.ReportEachBody == nil && !node.MarkedPending && !trackedFunctionError { + if !node.NodeType.Is(types.NodeTypeReportBeforeEach|types.NodeTypeReportAfterEach|types.NodeTypeSynchronizedBeforeSuite|types.NodeTypeSynchronizedAfterSuite|types.NodeTypeReportAfterSuite) && node.Body == nil && !node.MarkedPending && !trackedFunctionError { appendError(types.GinkgoErrors.MissingBodyFunction(node.CodeLocation, nodeType)) } for _, arg := range remainingArgs { @@ -286,43 +334,15 @@ func NewNode(deprecationTracker *types.DeprecationTracker, nodeType types.NodeTy return node, errors } -func NewSynchronizedBeforeSuiteNode(proc1Body func() []byte, allProcsBody func([]byte), codeLocation types.CodeLocation) (Node, []error) { - return Node{ - ID: UniqueNodeID(), - NodeType: types.NodeTypeSynchronizedBeforeSuite, - SynchronizedBeforeSuiteProc1Body: proc1Body, - SynchronizedBeforeSuiteAllProcsBody: allProcsBody, - CodeLocation: codeLocation, - }, nil -} - -func NewSynchronizedAfterSuiteNode(allProcsBody func(), proc1Body func(), codeLocation types.CodeLocation) (Node, []error) { - return Node{ - ID: UniqueNodeID(), - NodeType: types.NodeTypeSynchronizedAfterSuite, - SynchronizedAfterSuiteAllProcsBody: allProcsBody, - SynchronizedAfterSuiteProc1Body: proc1Body, - CodeLocation: codeLocation, - }, nil -} - -func NewReportAfterSuiteNode(text string, body func(types.Report), codeLocation types.CodeLocation) (Node, []error) { - return Node{ - ID: UniqueNodeID(), - Text: text, - NodeType: types.NodeTypeReportAfterSuite, - ReportAfterSuiteBody: body, - CodeLocation: codeLocation, - }, nil -} - func NewCleanupNode(fail func(string, types.CodeLocation), args ...interface{}) (Node, []error) { baseOffset := 2 node := Node{ - ID: UniqueNodeID(), - NodeType: types.NodeTypeCleanupInvalid, - CodeLocation: types.NewCodeLocation(baseOffset), - NestingLevel: -1, + ID: UniqueNodeID(), + NodeType: types.NodeTypeCleanupInvalid, + CodeLocation: types.NewCodeLocation(baseOffset), + NestingLevel: -1, + PollProgressAfter: -1, + PollProgressInterval: -1, } remainingArgs := []interface{}{} for _, arg := range args { @@ -331,6 +351,10 @@ func NewCleanupNode(fail func(string, types.CodeLocation), args ...interface{}) node.CodeLocation = types.NewCodeLocation(baseOffset + int(arg.(Offset))) case t == reflect.TypeOf(types.CodeLocation{}): node.CodeLocation = arg.(types.CodeLocation) + case t == reflect.TypeOf(PollProgressAfter(0)): + node.PollProgressAfter = time.Duration(arg.(PollProgressAfter)) + case t == reflect.TypeOf(PollProgressInterval(0)): + node.PollProgressInterval = time.Duration(arg.(PollProgressInterval)) default: remainingArgs = append(remainingArgs, arg) } diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go index b59918a81..4a1c09461 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor.go @@ -143,7 +143,7 @@ func (interceptor *genericOutputInterceptor) ResumeIntercepting() { go startPipeFactory(interceptor.pipeChannel, interceptor.shutdown) } - // Now we make a pipe, we'll use this to redirect the input to the 1 and 2 file descriptors (this is how everything else in the world is tring to log to stdout and stderr) + // Now we make a pipe, we'll use this to redirect the input to the 1 and 2 file descriptors (this is how everything else in the world is string to log to stdout and stderr) // we get the pipe from our pipe factory. it runs in the background so we can request the next pipe while the spec being intercepted is running interceptor.pipe = <-interceptor.pipeChannel diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go index e875001c2..f5ae15b8b 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/output_interceptor_unix.go @@ -28,7 +28,7 @@ func (impl *dupSyscallOutputInterceptorImpl) CreateStdoutStderrClones() (*os.Fil // And then wrap the clone file descriptors in files. // One benefit of this (that we don't use yet) is that we can actually write - // to these files to emit output to the console evne though we're intercepting output + // to these files to emit output to the console even though we're intercepting output stdoutClone := os.NewFile(uintptr(stdoutCloneFD), "stdout-clone") stderrClone := os.NewFile(uintptr(stderrCloneFD), "stderr-clone") diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go index 7d5cb0b63..b417bf5b3 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/client_server.go @@ -49,6 +49,7 @@ type Client interface { FetchNextCounter() (int, error) PostAbort() error ShouldAbort() bool + PostEmitProgressReport(report types.ProgressReport) error Write(p []byte) (int, error) } diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go index d076d5d1c..ad9932f2a 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_client.go @@ -94,6 +94,10 @@ func (client *httpClient) PostSuiteDidEnd(report types.Report) error { return client.post("/suite-did-end", report) } +func (client *httpClient) PostEmitProgressReport(report types.ProgressReport) error { + return client.post("/progress-report", report) +} + func (client *httpClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { beforeSuiteState := BeforeSuiteState{ State: state, diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go index ca1dcdca5..fa3ac682a 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/http_server.go @@ -49,6 +49,7 @@ func (server *httpServer) Start() { mux.HandleFunc("/did-run", server.didRun) mux.HandleFunc("/suite-did-end", server.specSuiteDidEnd) mux.HandleFunc("/emit-output", server.emitOutput) + mux.HandleFunc("/progress-report", server.emitProgressReport) //synchronization endpoints mux.HandleFunc("/before-suite-completed", server.handleBeforeSuiteCompleted) @@ -155,6 +156,14 @@ func (server *httpServer) emitOutput(writer http.ResponseWriter, request *http.R server.handleError(server.handler.EmitOutput(output, &n), writer) } +func (server *httpServer) emitProgressReport(writer http.ResponseWriter, request *http.Request) { + var report types.ProgressReport + if !server.decode(writer, request, &report) { + return + } + server.handleError(server.handler.EmitProgressReport(report, voidReceiver), writer) +} + func (server *httpServer) handleBeforeSuiteCompleted(writer http.ResponseWriter, request *http.Request) { var beforeSuiteState BeforeSuiteState if !server.decode(writer, request, &beforeSuiteState) { diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go index 4e83b0970..fe93cc2b9 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/rpc_client.go @@ -72,6 +72,10 @@ func (client *rpcClient) Write(p []byte) (int, error) { return n, err } +func (client *rpcClient) PostEmitProgressReport(report types.ProgressReport) error { + return client.client.Call("Server.EmitProgressReport", report, voidReceiver) +} + func (client *rpcClient) PostSynchronizedBeforeSuiteCompleted(state types.SpecState, data []byte) error { beforeSuiteState := BeforeSuiteState{ State: state, diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go index ca471cf39..7c6e67b96 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/parallel_support/server_handler.go @@ -108,6 +108,13 @@ func (handler *ServerHandler) EmitOutput(output []byte, n *int) error { return err } +func (handler *ServerHandler) EmitProgressReport(report types.ProgressReport, _ *Void) error { + handler.lock.Lock() + defer handler.lock.Unlock() + handler.reporter.EmitProgressReport(report) + return nil +} + func (handler *ServerHandler) registerAlive(proc int, alive func() bool) { handler.lock.Lock() defer handler.lock.Unlock() diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go new file mode 100644 index 000000000..ed0146c69 --- /dev/null +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report.go @@ -0,0 +1,289 @@ +package internal + +import ( + "bufio" + "bytes" + "context" + "fmt" + "io" + "os" + "os/signal" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/onsi/ginkgo/v2/types" +) + +var _SOURCE_CACHE = map[string][]string{} + +type ProgressSignalRegistrar func(func()) context.CancelFunc + +func RegisterForProgressSignal(handler func()) context.CancelFunc { + signalChannel := make(chan os.Signal, 1) + if len(PROGRESS_SIGNALS) > 0 { + signal.Notify(signalChannel, PROGRESS_SIGNALS...) + } + ctx, cancel := context.WithCancel(context.Background()) + go func() { + for { + select { + case <-signalChannel: + handler() + case <-ctx.Done(): + signal.Stop(signalChannel) + return + } + } + }() + + return cancel +} + +type ProgressStepCursor struct { + Text string + CodeLocation types.CodeLocation + StartTime time.Time +} + +func NewProgressReport(isRunningInParallel bool, report types.SpecReport, currentNode Node, currentNodeStartTime time.Time, currentStep ProgressStepCursor, gwOutput string, sourceRoots []string, includeAll bool) (types.ProgressReport, error) { + pr := types.ProgressReport{ + ParallelProcess: report.ParallelProcess, + RunningInParallel: isRunningInParallel, + + Time: time.Now(), + + ContainerHierarchyTexts: report.ContainerHierarchyTexts, + LeafNodeText: report.LeafNodeText, + LeafNodeLocation: report.LeafNodeLocation, + SpecStartTime: report.StartTime, + + CurrentNodeType: currentNode.NodeType, + CurrentNodeText: currentNode.Text, + CurrentNodeLocation: currentNode.CodeLocation, + CurrentNodeStartTime: currentNodeStartTime, + + CurrentStepText: currentStep.Text, + CurrentStepLocation: currentStep.CodeLocation, + CurrentStepStartTime: currentStep.StartTime, + + CapturedGinkgoWriterOutput: gwOutput, + GinkgoWriterOffset: len(gwOutput), + } + + goroutines, err := extractRunningGoroutines() + if err != nil { + return pr, err + } + pr.Goroutines = goroutines + + // now we want to try to find goroutines of interest. these will be goroutines that have any function calls with code in packagesOfInterest: + packagesOfInterest := map[string]bool{} + packageFromFilename := func(filename string) string { + return filepath.Dir(filename) + } + addPackageFor := func(filename string) { + if filename != "" { + packagesOfInterest[packageFromFilename(filename)] = true + } + } + isPackageOfInterest := func(filename string) bool { + stackPackage := packageFromFilename(filename) + for packageOfInterest := range packagesOfInterest { + if strings.HasPrefix(stackPackage, packageOfInterest) { + return true + } + } + return false + } + for _, location := range report.ContainerHierarchyLocations { + addPackageFor(location.FileName) + } + addPackageFor(report.LeafNodeLocation.FileName) + addPackageFor(currentNode.CodeLocation.FileName) + addPackageFor(currentStep.CodeLocation.FileName) + + //First, we find the SpecGoroutine - this will be the goroutine that includes `runNode` + specGoRoutineIdx := -1 + runNodeFunctionCallIdx := -1 +OUTER: + for goroutineIdx, goroutine := range pr.Goroutines { + for functionCallIdx, functionCall := range goroutine.Stack { + if strings.Contains(functionCall.Function, "ginkgo/v2/internal.(*Suite).runNode.func") { + specGoRoutineIdx = goroutineIdx + runNodeFunctionCallIdx = functionCallIdx + break OUTER + } + } + } + + //Now, we find the first non-Ginkgo function call + if specGoRoutineIdx > -1 { + for runNodeFunctionCallIdx >= 0 { + fn := goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Function + file := goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Filename + // these are all things that could potentially happen from within ginkgo + if strings.Contains(fn, "ginkgo/v2/internal") || strings.Contains(fn, "reflect.Value") || strings.Contains(file, "ginkgo/table_dsl") || strings.Contains(file, "ginkgo/core_dsl") { + runNodeFunctionCallIdx-- + continue + } + if strings.Contains(goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Function, "ginkgo/table_dsl") { + + } + //found it! lets add its package of interest + addPackageFor(goroutines[specGoRoutineIdx].Stack[runNodeFunctionCallIdx].Filename) + break + } + } + + ginkgoEntryPointIdx := -1 +OUTER_GINKGO_ENTRY_POINT: + for goroutineIdx, goroutine := range pr.Goroutines { + for _, functionCall := range goroutine.Stack { + if strings.Contains(functionCall.Function, "ginkgo/v2.RunSpecs") { + ginkgoEntryPointIdx = goroutineIdx + break OUTER_GINKGO_ENTRY_POINT + } + } + } + + // Now we go through all goroutines and highlight any lines with packages in `packagesOfInterest` + // Any goroutines with highlighted lines end up in the HighlightGoRoutines + for goroutineIdx, goroutine := range pr.Goroutines { + if goroutineIdx == ginkgoEntryPointIdx { + continue + } + if goroutineIdx == specGoRoutineIdx { + pr.Goroutines[goroutineIdx].IsSpecGoroutine = true + } + for functionCallIdx, functionCall := range goroutine.Stack { + if isPackageOfInterest(functionCall.Filename) { + goroutine.Stack[functionCallIdx].Highlight = true + goroutine.Stack[functionCallIdx].Source, goroutine.Stack[functionCallIdx].SourceHighlight = fetchSource(functionCall.Filename, functionCall.Line, 2, sourceRoots) + } + } + } + + if !includeAll { + goroutines := []types.Goroutine{pr.SpecGoroutine()} + goroutines = append(goroutines, pr.HighlightedGoroutines()...) + pr.Goroutines = goroutines + } + + return pr, nil +} + +func extractRunningGoroutines() ([]types.Goroutine, error) { + var stack []byte + for size := 64 * 1024; ; size *= 2 { + stack = make([]byte, size) + if n := runtime.Stack(stack, true); n < size { + stack = stack[:n] + break + } + } + + r := bufio.NewReader(bytes.NewReader(stack)) + out := []types.Goroutine{} + idx := -1 + for { + line, err := r.ReadString('\n') + if err == io.EOF { + break + } + + line = strings.TrimSuffix(line, "\n") + + //skip blank lines + if line == "" { + continue + } + + //parse headers for new goroutine frames + if strings.HasPrefix(line, "goroutine") { + out = append(out, types.Goroutine{}) + idx = len(out) - 1 + + line = strings.TrimPrefix(line, "goroutine ") + line = strings.TrimSuffix(line, ":") + fields := strings.SplitN(line, " ", 2) + if len(fields) != 2 { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid goroutine frame header: %s", line)) + } + out[idx].ID, err = strconv.ParseUint(fields[0], 10, 64) + if err != nil { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid goroutine ID: %s", fields[1])) + } + + out[idx].State = strings.TrimSuffix(strings.TrimPrefix(fields[1], "["), "]") + continue + } + + //if we are here we must be at a function call entry in the stack + functionCall := types.FunctionCall{ + Function: strings.TrimPrefix(line, "created by "), // no need to track 'created by' + } + + line, err = r.ReadString('\n') + line = strings.TrimSuffix(line, "\n") + if err == io.EOF { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid function call: %s -- missing file name and line number", functionCall.Function)) + } + line = strings.TrimLeft(line, " \t") + fields := strings.SplitN(line, ":", 2) + if len(fields) != 2 { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid filename nad line number: %s", line)) + } + functionCall.Filename = fields[0] + line = strings.Split(fields[1], " ")[0] + lineNumber, err := strconv.ParseInt(line, 10, 64) + functionCall.Line = int(lineNumber) + if err != nil { + return nil, types.GinkgoErrors.FailedToParseStackTrace(fmt.Sprintf("Invalid function call line number: %s\n%s", line, err.Error())) + } + out[idx].Stack = append(out[idx].Stack, functionCall) + } + + return out, nil +} + +func fetchSource(filename string, lineNumber int, span int, configuredSourceRoots []string) ([]string, int) { + if filename == "" { + return []string{}, 0 + } + + var lines []string + var ok bool + if lines, ok = _SOURCE_CACHE[filename]; !ok { + sourceRoots := []string{""} + sourceRoots = append(sourceRoots, configuredSourceRoots...) + var data []byte + var err error + var found bool + for _, root := range sourceRoots { + data, err = os.ReadFile(filepath.Join(root, filename)) + if err == nil { + found = true + break + } + } + if !found { + return []string{}, 0 + } + lines = strings.Split(string(data), "\n") + _SOURCE_CACHE[filename] = lines + } + + startIndex := lineNumber - span - 1 + endIndex := startIndex + span + span + 1 + if startIndex < 0 { + startIndex = 0 + } + if endIndex > len(lines) { + endIndex = len(lines) + } + highlightIndex := lineNumber - 1 - startIndex + return lines[startIndex:endIndex], highlightIndex +} diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go new file mode 100644 index 000000000..61e0ed306 --- /dev/null +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_bsd.go @@ -0,0 +1,11 @@ +//go:build freebsd || openbsd || netbsd || darwin || dragonfly +// +build freebsd openbsd netbsd darwin dragonfly + +package internal + +import ( + "os" + "syscall" +) + +var PROGRESS_SIGNALS = []os.Signal{syscall.SIGINFO, syscall.SIGUSR1} diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go new file mode 100644 index 000000000..ad30de459 --- /dev/null +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_unix.go @@ -0,0 +1,11 @@ +//go:build linux || solaris +// +build linux solaris + +package internal + +import ( + "os" + "syscall" +) + +var PROGRESS_SIGNALS = []os.Signal{syscall.SIGUSR1} diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go new file mode 100644 index 000000000..0eca2516a --- /dev/null +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/progress_report_win.go @@ -0,0 +1,8 @@ +//go:build windows +// +build windows + +package internal + +import "os" + +var PROGRESS_SIGNALS = []os.Signal{} diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/suite.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/suite.go index e27a29ddb..dfafa43f8 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/suite.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/internal/suite.go @@ -36,20 +36,35 @@ type Suite struct { interruptHandler interrupt_handler.InterruptHandlerInterface config types.SuiteConfig - skipAll bool - report types.Report - currentSpecReport types.SpecReport - currentSpecReportUserAccessLock *sync.Mutex - currentNode Node + skipAll bool + report types.Report + currentSpecReport types.SpecReport + currentNode Node + currentNodeStartTime time.Time + + progressStepCursor ProgressStepCursor + + /* + We don't need to lock around all operations. Just those that *could* happen concurrently. + + Suite, generally, only runs one node at a time - and so the possibiity for races is small. In fact, the presence of a race usually indicates the user has launched a goroutine that has leaked past the node it was launched in. + + However, there are some operations that can happen concurrently: + + - AddReportEntry and CurrentSpecReport can be accessed at any point by the user - including in goroutines that outlive the node intentionally (see, e.g. #1020). They both form a self-contained read-write pair and so a lock in them is sufficent. + - generateProgressReport can be invoked at any point in time by an interrupt or a progres poll. Moreover, it requires access to currentSpecReport, currentNode, currentNodeStartTime, and progressStepCursor. To make it threadsafe we need to lock around generateProgressReport when we read those variables _and_ everywhere those variables are *written*. In general we don't need to worry about all possible field writes to these variables as what `generateProgressReport` does with these variables is fairly selective (hence the name of the lock). Specifically, we dont' need to lock around state and failure message changes on `currentSpecReport` - just the setting of the variable itself. + */ + selectiveLock *sync.Mutex client parallel_support.Client } func NewSuite() *Suite { return &Suite{ - tree: &TreeNode{}, - phase: PhaseBuildTopLevel, - currentSpecReportUserAccessLock: &sync.Mutex{}, + tree: &TreeNode{}, + phase: PhaseBuildTopLevel, + + selectiveLock: &sync.Mutex{}, } } @@ -66,7 +81,7 @@ func (suite *Suite) BuildTree() error { return nil } -func (suite *Suite) Run(description string, suiteLabels Labels, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, suiteConfig types.SuiteConfig) (bool, bool) { +func (suite *Suite) Run(description string, suiteLabels Labels, suitePath string, failer *Failer, reporter reporters.Reporter, writer WriterInterface, outputInterceptor OutputInterceptor, interruptHandler interrupt_handler.InterruptHandlerInterface, client parallel_support.Client, progressSignalRegistrar ProgressSignalRegistrar, suiteConfig types.SuiteConfig) (bool, bool) { if suite.phase != PhaseBuildTree { panic("cannot run before building the tree = call suite.BuildTree() first") } @@ -83,8 +98,12 @@ func (suite *Suite) Run(description string, suiteLabels Labels, suitePath string suite.interruptHandler = interruptHandler suite.config = suiteConfig + cancelProgressHandler := progressSignalRegistrar(suite.handleProgressSignal) + success := suite.runSpecs(description, suiteLabels, suitePath, hasProgrammaticFocus, specs) + cancelProgressHandler() + return success, hasProgrammaticFocus } @@ -211,12 +230,23 @@ func (suite *Suite) pushCleanupNode(node Node) error { return nil } +/* + Pushing and popping the Step Cursor stack +*/ + +func (suite *Suite) SetProgressStepCursor(cursor ProgressStepCursor) { + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() + + suite.progressStepCursor = cursor +} + /* Spec Running methods - used during PhaseRun */ func (suite *Suite) CurrentSpecReport() types.SpecReport { - suite.currentSpecReportUserAccessLock.Lock() - defer suite.currentSpecReportUserAccessLock.Unlock() + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() report := suite.currentSpecReport if suite.writer != nil { report.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) @@ -227,8 +257,8 @@ func (suite *Suite) CurrentSpecReport() types.SpecReport { } func (suite *Suite) AddReportEntry(entry ReportEntry) error { - suite.currentSpecReportUserAccessLock.Lock() - defer suite.currentSpecReportUserAccessLock.Unlock() + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() if suite.phase != PhaseRun { return types.GinkgoErrors.AddReportEntryNotDuringRunPhase(entry.Location) } @@ -236,6 +266,37 @@ func (suite *Suite) AddReportEntry(entry ReportEntry) error { return nil } +func (suite *Suite) generateProgressReport(fullReport bool) types.ProgressReport { + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() + + stepCursor := suite.progressStepCursor + + gwOutput := suite.currentSpecReport.CapturedGinkgoWriterOutput + string(suite.writer.Bytes()) + pr, err := NewProgressReport(suite.isRunningInParallel(), suite.currentSpecReport, suite.currentNode, suite.currentNodeStartTime, stepCursor, gwOutput, suite.config.SourceRoots, fullReport) + + if err != nil { + fmt.Printf("{{red}}Failed to generate progress report:{{/}}\n%s\n", err.Error()) + } + return pr +} + +func (suite *Suite) handleProgressSignal() { + report := suite.generateProgressReport(false) + + suite.selectiveLock.Lock() + suite.currentSpecReport.ProgressReports = append(suite.currentSpecReport.ProgressReports, report.WithoutCapturedGinkgoWriterOutput()) + suite.selectiveLock.Unlock() + + suite.reporter.EmitProgressReport(report) + if suite.isRunningInParallel() { + err := suite.client.PostEmitProgressReport(report) + if err != nil { + fmt.Println(err.Error()) + } + } +} + func (suite *Suite) isRunningInParallel() bool { return suite.config.ParallelTotal > 1 } @@ -344,11 +405,14 @@ func (suite *Suite) runBeforeSuite(numSpecsThatWillBeRun int) { interruptStatus := suite.interruptHandler.Status() beforeSuiteNode := suite.suiteNodes.FirstNodeWithType(types.NodeTypeBeforeSuite | types.NodeTypeSynchronizedBeforeSuite) if !beforeSuiteNode.IsZero() && !interruptStatus.Interrupted && numSpecsThatWillBeRun > 0 { + suite.selectiveLock.Lock() suite.currentSpecReport = types.SpecReport{ LeafNodeType: beforeSuiteNode.NodeType, LeafNodeLocation: beforeSuiteNode.CodeLocation, ParallelProcess: suite.config.ParallelProcess, } + suite.selectiveLock.Unlock() + suite.reporter.WillRun(suite.currentSpecReport) suite.runSuiteNode(beforeSuiteNode, interruptStatus.Channel) if suite.currentSpecReport.State.Is(types.SpecStateSkipped) { @@ -362,11 +426,14 @@ func (suite *Suite) runBeforeSuite(numSpecsThatWillBeRun int) { func (suite *Suite) runAfterSuiteCleanup(numSpecsThatWillBeRun int) { afterSuiteNode := suite.suiteNodes.FirstNodeWithType(types.NodeTypeAfterSuite | types.NodeTypeSynchronizedAfterSuite) if !afterSuiteNode.IsZero() && numSpecsThatWillBeRun > 0 { + suite.selectiveLock.Lock() suite.currentSpecReport = types.SpecReport{ LeafNodeType: afterSuiteNode.NodeType, LeafNodeLocation: afterSuiteNode.CodeLocation, ParallelProcess: suite.config.ParallelProcess, } + suite.selectiveLock.Unlock() + suite.reporter.WillRun(suite.currentSpecReport) suite.runSuiteNode(afterSuiteNode, suite.interruptHandler.Status().Channel) suite.processCurrentSpecReport() @@ -375,11 +442,14 @@ func (suite *Suite) runAfterSuiteCleanup(numSpecsThatWillBeRun int) { afterSuiteCleanup := suite.cleanupNodes.WithType(types.NodeTypeCleanupAfterSuite).Reverse() if len(afterSuiteCleanup) > 0 { for _, cleanupNode := range afterSuiteCleanup { + suite.selectiveLock.Lock() suite.currentSpecReport = types.SpecReport{ LeafNodeType: cleanupNode.NodeType, LeafNodeLocation: cleanupNode.CodeLocation, ParallelProcess: suite.config.ParallelProcess, } + suite.selectiveLock.Unlock() + suite.reporter.WillRun(suite.currentSpecReport) suite.runSuiteNode(cleanupNode, suite.interruptHandler.Status().Channel) suite.processCurrentSpecReport() @@ -389,12 +459,15 @@ func (suite *Suite) runAfterSuiteCleanup(numSpecsThatWillBeRun int) { func (suite *Suite) runReportAfterSuite() { for _, node := range suite.suiteNodes.WithType(types.NodeTypeReportAfterSuite) { + suite.selectiveLock.Lock() suite.currentSpecReport = types.SpecReport{ LeafNodeType: node.NodeType, LeafNodeLocation: node.CodeLocation, LeafNodeText: node.Text, ParallelProcess: suite.config.ParallelProcess, } + suite.selectiveLock.Unlock() + suite.reporter.WillRun(suite.currentSpecReport) suite.runReportAfterSuiteNode(node, suite.report) suite.processCurrentSpecReport() @@ -564,9 +637,16 @@ func (suite *Suite) runNode(node Node, interruptChannel chan interface{}, text s suite.cleanupNodes = suite.cleanupNodes.WithoutNode(node) } + suite.selectiveLock.Lock() suite.currentNode = node + suite.currentNodeStartTime = time.Now() + suite.progressStepCursor = ProgressStepCursor{} + suite.selectiveLock.Unlock() defer func() { + suite.selectiveLock.Lock() suite.currentNode = Node{} + suite.currentNodeStartTime = time.Time{} + suite.selectiveLock.Unlock() }() if suite.config.EmitSpecProgress && !node.MarkedSuppressProgressReporting { @@ -606,17 +686,44 @@ func (suite *Suite) runNode(node Node, interruptChannel chan interface{}, text s finished = true }() - select { - case outcome := <-outcomeC: - failureFromRun := <-failureC - if outcome == types.SpecStatePassed { - return outcome, types.Failure{} + var emitProgressNow <-chan time.Time + var progressPoller *time.Timer + var pollProgressAfter, pollProgressInterval = suite.config.PollProgressAfter, suite.config.PollProgressInterval + if node.PollProgressAfter >= 0 { + pollProgressAfter = node.PollProgressAfter + } + if node.PollProgressInterval >= 0 { + pollProgressInterval = node.PollProgressInterval + } + + if pollProgressAfter > 0 { + progressPoller = time.NewTimer(pollProgressAfter) + emitProgressNow = progressPoller.C + defer progressPoller.Stop() + } + + for { + select { + case outcome := <-outcomeC: + failureFromRun := <-failureC + if outcome == types.SpecStatePassed { + return outcome, types.Failure{} + } + failure.Message, failure.Location, failure.ForwardedPanic = failureFromRun.Message, failureFromRun.Location, failureFromRun.ForwardedPanic + return outcome, failure + case <-interruptChannel: + reason, includeProgressReport := suite.interruptHandler.InterruptMessage() + failure.Message, failure.Location = reason, node.CodeLocation + if includeProgressReport { + failure.ProgressReport = suite.generateProgressReport(true).WithoutCapturedGinkgoWriterOutput() + } + return types.SpecStateInterrupted, failure + case <-emitProgressNow: + suite.handleProgressSignal() + if pollProgressInterval > 0 { + progressPoller.Reset(pollProgressInterval) + } } - failure.Message, failure.Location, failure.ForwardedPanic = failureFromRun.Message, failureFromRun.Location, failureFromRun.ForwardedPanic - return outcome, failure - case <-interruptChannel: - failure.Message, failure.Location = suite.interruptHandler.InterruptMessageWithStackTraces(), node.CodeLocation - return types.SpecStateInterrupted, failure } } diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go index 0edd44bce..ccd3317a5 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/default_reporter.go @@ -12,6 +12,7 @@ import ( "io" "runtime" "strings" + "time" "github.com/onsi/ginkgo/v2/formatter" "github.com/onsi/ginkgo/v2/types" @@ -208,9 +209,7 @@ func (r *DefaultReporter) DidRun(report types.SpecReport) { //Emit Captured GinkgoWriter Output if emitGinkgoWriterOutput && hasGW { r.emitBlock("\n") - r.emitBlock(r.fi(1, "{{gray}}Begin Captured GinkgoWriter Output >>{{/}}")) - r.emitBlock(r.fi(2, "%s", report.CapturedGinkgoWriterOutput)) - r.emitBlock(r.fi(1, "{{gray}}<< End Captured GinkgoWriter Output{{/}}")) + r.emitGinkgoWriterOutput(1, report.CapturedGinkgoWriterOutput, 0) } if hasEmittableReports { @@ -244,6 +243,11 @@ func (r *DefaultReporter) DidRun(report types.SpecReport) { r.emitBlock(r.fi(1, highlightColor+"Full Stack Trace{{/}}")) r.emitBlock(r.fi(2, "%s", report.Failure.Location.FullStackTrace)) } + + if !report.Failure.ProgressReport.IsZero() { + r.emitBlock("\n") + r.emitProgressReport(1, false, report.Failure.ProgressReport) + } } r.emitDelimiter() @@ -314,6 +318,143 @@ func (r *DefaultReporter) SuiteDidEnd(report types.Report) { } } +func (r *DefaultReporter) EmitProgressReport(report types.ProgressReport) { + r.emitDelimiter() + + if report.RunningInParallel { + r.emit(r.f("{{coral}}Progress Report for Ginkgo Process #{{bold}}%d{{/}}\n", report.ParallelProcess)) + } + r.emitProgressReport(0, true, report) + r.emitDelimiter() +} + +func (r *DefaultReporter) emitProgressReport(indent uint, emitGinkgoWriterOutput bool, report types.ProgressReport) { + if report.LeafNodeText != "" { + if len(report.ContainerHierarchyTexts) > 0 { + r.emit(r.fi(indent, r.cycleJoin(report.ContainerHierarchyTexts, " "))) + r.emit(" ") + } + r.emit(r.f("{{bold}}{{orange}}%s{{/}} (Spec Runtime: %s)\n", report.LeafNodeText, report.Time.Sub(report.SpecStartTime).Round(time.Millisecond))) + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.LeafNodeLocation)) + indent += 1 + } + if report.CurrentNodeType != types.NodeTypeInvalid { + r.emit(r.fi(indent, "In {{bold}}{{orange}}[%s]{{/}}", report.CurrentNodeType)) + if report.CurrentNodeText != "" && !report.CurrentNodeType.Is(types.NodeTypeIt) { + r.emit(r.f(" {{bold}}{{orange}}%s{{/}}", report.CurrentNodeText)) + } + + r.emit(r.f(" (Node Runtime: %s)\n", report.Time.Sub(report.CurrentNodeStartTime).Round(time.Millisecond))) + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentNodeLocation)) + indent += 1 + } + if report.CurrentStepText != "" { + r.emit(r.fi(indent, "At {{bold}}{{orange}}[By Step] %s{{/}} (Step Runtime: %s)\n", report.CurrentStepText, report.Time.Sub(report.CurrentStepStartTime).Round(time.Millisecond))) + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", report.CurrentStepLocation)) + indent += 1 + } + + if indent > 0 { + indent -= 1 + } + + if emitGinkgoWriterOutput && report.CapturedGinkgoWriterOutput != "" && (report.RunningInParallel || r.conf.Verbosity().LT(types.VerbosityLevelVerbose)) { + r.emit("\n") + r.emitGinkgoWriterOutput(indent, report.CapturedGinkgoWriterOutput, 10) + } + + if !report.SpecGoroutine().IsZero() { + r.emit("\n") + r.emit(r.fi(indent, "{{bold}}{{underline}}Spec Goroutine{{/}}\n")) + r.emitGoroutines(indent, report.SpecGoroutine()) + } + + highlightedGoroutines := report.HighlightedGoroutines() + if len(highlightedGoroutines) > 0 { + r.emit("\n") + r.emit(r.fi(indent, "{{bold}}{{underline}}Goroutines of Interest{{/}}\n")) + r.emitGoroutines(indent, highlightedGoroutines...) + } + + otherGoroutines := report.OtherGoroutines() + if len(otherGoroutines) > 0 { + r.emit("\n") + r.emit(r.fi(indent, "{{gray}}{{bold}}{{underline}}Other Goroutines{{/}}\n")) + r.emitGoroutines(indent, otherGoroutines...) + } +} + +func (r *DefaultReporter) emitGinkgoWriterOutput(indent uint, output string, limit int) { + r.emitBlock(r.fi(indent, "{{gray}}Begin Captured GinkgoWriter Output >>{{/}}")) + if limit == 0 { + r.emitBlock(r.fi(indent+1, "%s", output)) + } else { + lines := strings.Split(output, "\n") + if len(lines) <= limit { + r.emitBlock(r.fi(indent+1, "%s", output)) + } else { + r.emitBlock(r.fi(indent+1, "{{gray}}...{{/}}")) + for _, line := range lines[len(lines)-limit-1:] { + r.emitBlock(r.fi(indent+1, "%s", line)) + } + } + } + r.emitBlock(r.fi(indent, "{{gray}}<< End Captured GinkgoWriter Output{{/}}")) +} + +func (r *DefaultReporter) emitGoroutines(indent uint, goroutines ...types.Goroutine) { + for idx, g := range goroutines { + color := "{{gray}}" + if g.HasHighlights() { + color = "{{orange}}" + } + r.emit(r.fi(indent, color+"goroutine %d [%s]{{/}}\n", g.ID, g.State)) + for _, fc := range g.Stack { + if fc.Highlight { + r.emit(r.fi(indent, color+"{{bold}}> %s{{/}}\n", fc.Function)) + r.emit(r.fi(indent+2, color+"{{bold}}%s:%d{{/}}\n", fc.Filename, fc.Line)) + r.emitSource(indent+3, fc) + } else { + r.emit(r.fi(indent+1, "{{gray}}%s{{/}}\n", fc.Function)) + r.emit(r.fi(indent+2, "{{gray}}%s:%d{{/}}\n", fc.Filename, fc.Line)) + } + } + + if idx+1 < len(goroutines) { + r.emit("\n") + } + } +} + +func (r *DefaultReporter) emitSource(indent uint, fc types.FunctionCall) { + lines := fc.Source + if len(lines) == 0 { + return + } + + lTrim := 100000 + for _, line := range lines { + lTrimLine := len(line) - len(strings.TrimLeft(line, " \t")) + if lTrimLine < lTrim && len(line) > 0 { + lTrim = lTrimLine + } + } + if lTrim == 100000 { + lTrim = 0 + } + + for idx, line := range lines { + if len(line) > lTrim { + line = line[lTrim:] + } + if idx == fc.SourceHighlight { + r.emit(r.fi(indent, "{{bold}}{{orange}}> %s{{/}}\n", line)) + } else { + r.emit(r.fi(indent, "| %s\n", line)) + } + } +} + /* Emitting to the writer */ func (r *DefaultReporter) emit(s string) { if len(s) > 0 { diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go index febcc650b..0f165069a 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/junit_report.go @@ -171,8 +171,8 @@ func GenerateJUnitReport(report types.Report, dst string) error { Classname: report.SuiteDescription, Status: spec.State.String(), Time: spec.RunTime.Seconds(), - SystemOut: systemOutForUnstructureReporters(spec), - SystemErr: spec.CapturedGinkgoWriterOutput, + SystemOut: systemOutForUnstructuredReporters(spec), + SystemErr: systemErrForUnstructuredReporters(spec), } suite.Tests += 1 @@ -198,7 +198,7 @@ func GenerateJUnitReport(report types.Report, dst string) error { test.Error = &JUnitError{ Message: "interrupted", Type: "interrupted", - Description: spec.Failure.Message, + Description: interruptDescriptionForUnstructuredReporters(spec.Failure), } suite.Errors += 1 case types.SpecStateAborted: @@ -278,7 +278,38 @@ func MergeAndCleanupJUnitReports(sources []string, dst string) ([]string, error) return messages, f.Close() } -func systemOutForUnstructureReporters(spec types.SpecReport) string { +func interruptDescriptionForUnstructuredReporters(failure types.Failure) string { + out := &strings.Builder{} + out.WriteString(failure.Message + "\n") + NewDefaultReporter(types.ReporterConfig{NoColor: true}, out).EmitProgressReport(failure.ProgressReport) + return out.String() +} + +func systemErrForUnstructuredReporters(spec types.SpecReport) string { + out := &strings.Builder{} + gw := spec.CapturedGinkgoWriterOutput + cursor := 0 + for _, pr := range spec.ProgressReports { + if cursor < pr.GinkgoWriterOffset { + if pr.GinkgoWriterOffset < len(gw) { + out.WriteString(gw[cursor:pr.GinkgoWriterOffset]) + cursor = pr.GinkgoWriterOffset + } else if cursor < len(gw) { + out.WriteString(gw[cursor:]) + cursor = len(gw) + } + } + NewDefaultReporter(types.ReporterConfig{NoColor: true}, out).EmitProgressReport(pr) + } + + if cursor < len(gw) { + out.WriteString(gw[cursor:]) + } + + return out.String() +} + +func systemOutForUnstructuredReporters(spec types.SpecReport) string { systemOut := spec.CapturedStdOutErr if len(spec.ReportEntries) > 0 { systemOut += "\nReport Entries:\n" diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go index 29f84e7c1..f79f005db 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/reporter.go @@ -9,11 +9,13 @@ type Reporter interface { WillRun(report types.SpecReport) DidRun(report types.SpecReport) SuiteDidEnd(report types.Report) + EmitProgressReport(progressReport types.ProgressReport) } type NoopReporter struct{} -func (n NoopReporter) SuiteWillBegin(report types.Report) {} -func (n NoopReporter) WillRun(report types.SpecReport) {} -func (n NoopReporter) DidRun(report types.SpecReport) {} -func (n NoopReporter) SuiteDidEnd(report types.Report) {} +func (n NoopReporter) SuiteWillBegin(report types.Report) {} +func (n NoopReporter) WillRun(report types.SpecReport) {} +func (n NoopReporter) DidRun(report types.SpecReport) {} +func (n NoopReporter) SuiteDidEnd(report types.Report) {} +func (n NoopReporter) EmitProgressReport(progressReport types.ProgressReport) {} diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go index f9b1117ca..00b038769 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporters/teamcity_report.go @@ -66,14 +66,14 @@ func GenerateTeamcityReport(report types.Report, dst string) error { details := fmt.Sprintf("%s\n%s", spec.Failure.Location.String(), spec.Failure.Location.FullStackTrace) fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='panicked - %s' details='%s']\n", name, tcEscape(spec.Failure.ForwardedPanic), tcEscape(details)) case types.SpecStateInterrupted: - fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='interrupted' details='%s']\n", name, tcEscape(spec.Failure.Message)) + fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='interrupted' details='%s']\n", name, tcEscape(interruptDescriptionForUnstructuredReporters(spec.Failure))) case types.SpecStateAborted: details := fmt.Sprintf("%s\n%s", spec.Failure.Location.String(), spec.Failure.Location.FullStackTrace) fmt.Fprintf(f, "##teamcity[testFailed name='%s' message='aborted - %s' details='%s']\n", name, tcEscape(spec.Failure.Message), tcEscape(details)) } - fmt.Fprintf(f, "##teamcity[testStdOut name='%s' out='%s']\n", name, tcEscape(systemOutForUnstructureReporters(spec))) - fmt.Fprintf(f, "##teamcity[testStdErr name='%s' out='%s']\n", name, tcEscape(spec.CapturedGinkgoWriterOutput)) + fmt.Fprintf(f, "##teamcity[testStdOut name='%s' out='%s']\n", name, tcEscape(systemOutForUnstructuredReporters(spec))) + fmt.Fprintf(f, "##teamcity[testStdErr name='%s' out='%s']\n", name, tcEscape(systemErrForUnstructuredReporters(spec))) fmt.Fprintf(f, "##teamcity[testFinished name='%s' duration='%d']\n", name, int(spec.RunTime.Seconds()*1000.0)) } fmt.Fprintf(f, "##teamcity[testSuiteFinished name='%s']\n", tcEscape(report.SuiteDescription)) diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go index 8750236d9..afc151b13 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/reporting_dsl.go @@ -115,8 +115,10 @@ You cannot nest any other Ginkgo nodes within a ReportAfterSuite node's closure. You can learn more about ReportAfterSuite here: https://onsi.github.io/ginkgo/#generating-reports-programmatically You can learn more about Ginkgo's reporting infrastructure, including generating reports with the CLI here: https://onsi.github.io/ginkgo/#generating-machine-readable-reports */ -func ReportAfterSuite(text string, body func(Report)) bool { - return pushNode(internal.NewReportAfterSuiteNode(text, body, types.NewCodeLocation(1))) +func ReportAfterSuite(text string, body func(Report), args ...interface{}) bool { + combinedArgs := []interface{}{body} + combinedArgs = append(combinedArgs, args...) + return pushNode(internal.NewNode(deprecationTracker, types.NodeTypeReportAfterSuite, text, combinedArgs...)) } func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.ReporterConfig) { @@ -151,7 +153,8 @@ func registerReportAfterSuiteNodeForAutogeneratedReports(reporterConfig types.Re if reporterConfig.TeamcityReport != "" { flags = append(flags, "--teamcity-report") } - pushNode(internal.NewReportAfterSuiteNode( + pushNode(internal.NewNode( + deprecationTracker, types.NodeTypeReportAfterSuite, fmt.Sprintf("Autogenerated ReportAfterSuite for %s", strings.Join(flags, " ")), body, types.NewCustomCodeLocation("autogenerated by Ginkgo"), diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/config.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/config.go index 07ef4c3a7..438c947c8 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/config.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/config.go @@ -28,8 +28,11 @@ type SuiteConfig struct { FlakeAttempts int EmitSpecProgress bool DryRun bool + PollProgressAfter time.Duration + PollProgressInterval time.Duration Timeout time.Duration OutputInterceptorMode string + SourceRoots []string ParallelProcess int ParallelTotal int @@ -272,6 +275,12 @@ var SuiteConfigFlags = GinkgoFlags{ Usage: "If set, ginkgo will walk the test hierarchy without actually running anything. Best paired with -v."}, {KeyPath: "S.EmitSpecProgress", Name: "progress", SectionKey: "debug", Usage: "If set, ginkgo will emit progress information as each spec runs to the GinkgoWriter."}, + {KeyPath: "S.PollProgressAfter", Name: "poll-progress-after", SectionKey: "debug", UsageDefaultValue: "0", + Usage: "Emit node progress reports periodically if node hasn't completed after this duration."}, + {KeyPath: "S.PollProgressInterval", Name: "poll-progress-interval", SectionKey: "debug", UsageDefaultValue: "10s", + Usage: "The rate at which to emit node progress reports after poll-progress-after has elapsed."}, + {KeyPath: "S.SourceRoots", Name: "source-root", SectionKey: "debug", + Usage: "The location to look for source code when generating progress reports. You can pass multiple --source-root flags."}, {KeyPath: "S.Timeout", Name: "timeout", SectionKey: "debug", UsageDefaultValue: "1h", Usage: "Test suite fails if it does not complete within the specified timeout."}, {KeyPath: "S.OutputInterceptorMode", Name: "output-interceptor-mode", SectionKey: "debug", UsageArgument: "dup, swap, or none", diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/errors.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/errors.go index 40331d298..6806d6afc 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/errors.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/errors.go @@ -532,3 +532,12 @@ func (g ginkgoErrors) BothRepeatAndUntilItFails() error { Message: "--until-it-fails directs Ginkgo to rerun specs indefinitely until they fail. --repeat directs Ginkgo to rerun specs a set number of times. You can't set both... which would you like?", } } + +/* Stack-Trace parsing errors */ + +func (g ginkgoErrors) FailedToParseStackTrace(message string) error { + return GinkgoError{ + Heading: "Failed to Parse Stack Trace", + Message: message, + } +} diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/report_entry.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/report_entry.go index c64866c60..798bedc03 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/report_entry.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/report_entry.go @@ -50,7 +50,6 @@ func (rev ReportEntryValue) MarshalJSON() ([]byte, error) { }{ Representation: rev.String(), } - asJSON, err := json.Marshal(rev.raw) if err != nil { return nil, err @@ -98,7 +97,7 @@ type ReportEntry struct { Value ReportEntryValue } -// ColorableStringer is an interface that ReportEntry values can satisfy. If they do then ColorableStirng() is used to generate their representation. +// ColorableStringer is an interface that ReportEntry values can satisfy. If they do then ColorableString() is used to generate their representation. type ColorableStringer interface { ColorableString() string } @@ -121,6 +120,8 @@ func (entry ReportEntry) GetRawValue() interface{} { return entry.Value.GetRawValue() } + + type ReportEntries []ReportEntry func (re ReportEntries) HasVisibility(visibilities ...ReportEntryVisibility) bool { diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/types.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/types.go index f30d23c66..aec9062ed 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/types.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/types.go @@ -165,6 +165,9 @@ type SpecReport struct { // ReportEntries contains any reports added via `AddReportEntry` ReportEntries ReportEntries + + // ProgressReports contains any progress reports generated during this spec. These can either be manually triggered, or automatically generated by Ginkgo via the PollProgressAfter() decorator + ProgressReports []ProgressReport } func (report SpecReport) MarshalJSON() ([]byte, error) { @@ -184,9 +187,10 @@ func (report SpecReport) MarshalJSON() ([]byte, error) { ParallelProcess int Failure *Failure `json:",omitempty"` NumAttempts int - CapturedGinkgoWriterOutput string `json:",omitempty"` - CapturedStdOutErr string `json:",omitempty"` - ReportEntries ReportEntries `json:",omitempty"` + CapturedGinkgoWriterOutput string `json:",omitempty"` + CapturedStdOutErr string `json:",omitempty"` + ReportEntries ReportEntries `json:",omitempty"` + ProgressReports []ProgressReport `json:",omitempty"` }{ ContainerHierarchyTexts: report.ContainerHierarchyTexts, ContainerHierarchyLocations: report.ContainerHierarchyLocations, @@ -213,6 +217,9 @@ func (report SpecReport) MarshalJSON() ([]byte, error) { if len(report.ReportEntries) > 0 { out.ReportEntries = report.ReportEntries } + if len(report.ProgressReports) > 0 { + out.ProgressReports = report.ProgressReports + } return json.Marshal(out) } @@ -379,7 +386,7 @@ type Failure struct { // FailureNodeContext - one of three contexts describing the node in which the failure occurred: // FailureNodeIsLeafNode means the failure occurred in the leaf node of the associated SpecReport. None of the other FailureNode fields will be populated // 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. - // FailureNodeInContainer means the failure occurred in a non-leaf node that is defined within a container. FailureNodeType, FailureNodeLocaiton, and FailureNodeContainerIndex will be populated. + // FailureNodeInContainer means the failure occurred in a non-leaf node that is defined within a container. FailureNodeType, FailureNodeLocation, and FailureNodeContainerIndex will be populated. // // FailureNodeType will contain the NodeType of the node in which the failure occurred. // FailureNodeLocation will contain the CodeLocation of the node in which the failure occurred. @@ -388,10 +395,13 @@ type Failure struct { FailureNodeType NodeType FailureNodeLocation CodeLocation FailureNodeContainerIndex int + + //ProgressReport is populated if the spec was interrupted or timed out + ProgressReport ProgressReport } func (f Failure) IsZero() bool { - return f == Failure{} + return f.Message == "" && (f.Location == CodeLocation{}) } // FailureNodeContext captures the location context for the node containing the failing line of code @@ -469,6 +479,104 @@ func (ss SpecState) Is(states SpecState) bool { return ss&states != 0 } +// ProgressReport captures the progress of the current spec. It is, effectively, a structured Ginkgo-aware stack trace +type ProgressReport struct { + ParallelProcess int + RunningInParallel bool + + Time time.Time + + ContainerHierarchyTexts []string + LeafNodeText string + LeafNodeLocation CodeLocation + SpecStartTime time.Time + + CurrentNodeType NodeType + CurrentNodeText string + CurrentNodeLocation CodeLocation + CurrentNodeStartTime time.Time + + CurrentStepText string + CurrentStepLocation CodeLocation + CurrentStepStartTime time.Time + + CapturedGinkgoWriterOutput string `json:",omitempty"` + GinkgoWriterOffset int + + Goroutines []Goroutine +} + +func (pr ProgressReport) IsZero() bool { + return pr.CurrentNodeType == NodeTypeInvalid +} + +func (pr ProgressReport) SpecGoroutine() Goroutine { + for _, goroutine := range pr.Goroutines { + if goroutine.IsSpecGoroutine { + return goroutine + } + } + return Goroutine{} +} + +func (pr ProgressReport) HighlightedGoroutines() []Goroutine { + out := []Goroutine{} + for _, goroutine := range pr.Goroutines { + if goroutine.IsSpecGoroutine || !goroutine.HasHighlights() { + continue + } + out = append(out, goroutine) + } + return out +} + +func (pr ProgressReport) OtherGoroutines() []Goroutine { + out := []Goroutine{} + for _, goroutine := range pr.Goroutines { + if goroutine.IsSpecGoroutine || goroutine.HasHighlights() { + continue + } + out = append(out, goroutine) + } + return out +} + +func (pr ProgressReport) WithoutCapturedGinkgoWriterOutput() ProgressReport { + out := pr + out.CapturedGinkgoWriterOutput = "" + return out +} + +type Goroutine struct { + ID uint64 + State string + Stack []FunctionCall + IsSpecGoroutine bool +} + +func (g Goroutine) IsZero() bool { + return g.ID == 0 +} + +func (g Goroutine) HasHighlights() bool { + for _, fc := range g.Stack { + if fc.Highlight { + return true + } + } + + return false +} + +type FunctionCall struct { + Function string + Filename string + Line int + Highlight bool `json:",omitempty"` + Source []string `json:",omitempty"` + SourceHighlight int `json:",omitempty"` +} + // NodeType captures the type of a given Ginkgo Node type NodeType uint diff --git a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/version.go b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/version.go index c79901491..3974fdc26 100644 --- a/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/version.go +++ b/assets/catnip/vendor/github.com/onsi/ginkgo/v2/types/version.go @@ -1,3 +1,3 @@ package types -const VERSION = "2.1.6" +const VERSION = "2.2.0" diff --git a/assets/catnip/vendor/modules.txt b/assets/catnip/vendor/modules.txt index eb522bcab..d0b699b02 100644 --- a/assets/catnip/vendor/modules.txt +++ b/assets/catnip/vendor/modules.txt @@ -14,7 +14,7 @@ github.com/google/go-cmp/cmp/internal/value github.com/gorilla/mux # github.com/kr/pretty v0.1.0 ## explicit -# github.com/onsi/ginkgo/v2 v2.1.6 +# github.com/onsi/ginkgo/v2 v2.2.0 ## explicit; go 1.18 github.com/onsi/ginkgo/v2 github.com/onsi/ginkgo/v2/config