diff --git a/go.mod b/go.mod index 7ee880fd..0088e46f 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.27.3 require ( code.cloudfoundry.org/cfhttp/v2 v2.101.0 code.cloudfoundry.org/debugserver v0.121.0 - code.cloudfoundry.org/lager/v3 v3.93.0 + code.cloudfoundry.org/lager/v3 v3.94.0 code.cloudfoundry.org/runtimeschema v0.0.0-20240514235758-31be7684c5bf code.cloudfoundry.org/tlsconfig v0.70.0 github.com/cloudfoundry/dropsonde v1.1.0 - github.com/onsi/ginkgo/v2 v2.33.1 + github.com/onsi/ginkgo/v2 v2.34.0 github.com/onsi/gomega v1.44.0 github.com/tedsuo/ifrit v0.0.0-20260908181113-dd353a7daa27 github.com/tedsuo/rata v1.0.0 @@ -24,16 +24,16 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/pprof v0.0.0-20261026192309-dbd0ed30aa88 // indirect + github.com/google/pprof v0.0.0-20261102214848-0031fcd306b0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nxadm/tail v1.4.11 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect + go.yaml.in/yaml/v3 v3.0.6 // indirect golang.org/x/mod v0.42.0 // indirect - golang.org/x/net v0.61.0 // indirect + golang.org/x/net v0.62.0 // indirect golang.org/x/sync v0.24.0 // indirect golang.org/x/sys v0.49.0 // indirect golang.org/x/text v0.43.0 // indirect diff --git a/go.sum b/go.sum index 8a9dde64..d652cdab 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ code.cloudfoundry.org/debugserver v0.121.0 h1:uQ45ktm3BJOR9pX9XTw9uRgDC4wppD1LTo code.cloudfoundry.org/debugserver v0.121.0/go.mod h1:vLdd0kiCIkgJft0t1WBjJTrPHXckzpmUXFp5R/c9dtc= code.cloudfoundry.org/lager v2.0.0+incompatible h1:WZwDKDB2PLd/oL+USK4b4aEjUymIej9My2nUQ9oWEwQ= code.cloudfoundry.org/lager v2.0.0+incompatible/go.mod h1:O2sS7gKP3HM2iemG+EnwvyNQK7pTSC6Foi4QiMp9sSk= -code.cloudfoundry.org/lager/v3 v3.93.0 h1:RM8vU3xYa+CsBB9WIEiylcT0DTWKsZgy0qAF4iBJmX8= -code.cloudfoundry.org/lager/v3 v3.93.0/go.mod h1:WBwfI2ZWsjbaWHFZRrIOYPeAP1l9Vzkd3Oc1cFox0fM= +code.cloudfoundry.org/lager/v3 v3.94.0 h1:Q98+qcnG/JQjCNmzwjHpt7zB8g7RaMZU2gaQBYyHVv0= +code.cloudfoundry.org/lager/v3 v3.94.0/go.mod h1:Csec9lyiZSKNTL5/qh2GBiiPSLHGLweq3/EhPCjAV8U= code.cloudfoundry.org/locket v0.0.0-20221110203340-76a930295e59 h1:oglZYmMS7wqvH7KbTRCSEE5vA8tkIqaaC0cBIFXt0ns= code.cloudfoundry.org/locket v0.0.0-20221110203340-76a930295e59/go.mod h1:AwHLRkdXtttLXNB8RHgLfErJ2kKafH62AR2OClhy6xI= code.cloudfoundry.org/runtimeschema v0.0.0-20240514235758-31be7684c5bf h1:sTpfHaU+tgDeRO/fRnyr91gvf1FaJk3ZNiJHiHq4nko= @@ -53,8 +53,8 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20261026192309-dbd0ed30aa88 h1:BnzMe/RUfFezpFwXsOWHmSMNO8qt7755OL25C+AezGQ= -github.com/google/pprof v0.0.0-20261026192309-dbd0ed30aa88/go.mod h1:yUo1AMMpsPIDFGuwDK8nLcf8+vvAd5l2O6JDZ6hpwrM= +github.com/google/pprof v0.0.0-20261102214848-0031fcd306b0 h1:86rNkgYeJezkpuaJQidi1uAb467frc71yU2PIRDf2FA= +github.com/google/pprof v0.0.0-20261102214848-0031fcd306b0/go.mod h1:yUo1AMMpsPIDFGuwDK8nLcf8+vvAd5l2O6JDZ6hpwrM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= @@ -81,8 +81,8 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.33.1 h1:62XfWJbEIjPSCSDYrsCPP/SHcmH7O0M8ne8zfqQ2iz4= -github.com/onsi/ginkgo/v2 v2.33.1/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/ginkgo/v2 v2.34.0 h1:aN3Us823mHHTSWMIi8WIQTdYuSPhl2lWY46E/w3SiAk= +github.com/onsi/ginkgo/v2 v2.34.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= github.com/onsi/gomega v1.44.0 h1:eAiGl3Pw5jz5GQdDff0BcxYpAX1JxW8xD7mFUuwNfZQ= github.com/onsi/gomega v1.44.0/go.mod h1:e/C2HwaZ1DhvjzXXuFhcR7hY7Sh9pl7MmoWKEjzwcdA= github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg= @@ -116,8 +116,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.step.sm/crypto v0.91.0 h1:0mN0DwVOvUuh7VbyTnxABZuAkxwak/Grp8Y/b4YaZDU= go.step.sm/crypto v0.91.0/go.mod h1:NxxObRBymdbyXLoTCW5Ea6sLxuy5yI/xr9+hSBlbU/Q= -go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= -go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= +go.yaml.in/yaml/v3 v3.0.6 h1:zcDfvjXEqTQmpjniUBLc91xYJMB2c4QOFxEF/LCEckM= +go.yaml.in/yaml/v3 v3.0.6/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -131,8 +131,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw= -golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= +golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY= +golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md index a5c6fb86..0679ee9b 100644 --- a/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md +++ b/vendor/github.com/onsi/ginkgo/v2/CHANGELOG.md @@ -6,6 +6,15 @@ ### Maintenance +## 2.34.0 + +### Features +- `DeferOnFailure` registers a callback that runs only when a spec fails - as soon as the failing node returns and before any teardown (`JustAfterEach`, `AfterEach`, `AfterAll`, `DeferCleanup`) - so it can capture the state teardown is about to destroy. It takes exactly what `DeferCleanup` takes and shares its scoping: called in `BeforeEach`/`JustBeforeEach`/`It` it covers the spec, in `BeforeAll` the ordered container, and in `BeforeSuite` the whole suite. It is also available on `GinkgoT()`; note that this adds a method to `FullGinkgoTInterface`, so hand-written implementations of that interface need to add it. See https://onsi.github.io/ginkgo/#capturing-failure-state-deferonfailure + +### Fixes +- Progress reporters (`AttachProgressReporter`) are no longer called while Ginkgo holds an internal lock, so a reporter that calls `CurrentSpecReport()` or `AddReportEntry()` no longer stalls for 5 seconds and has its output dropped. +- `--sleep-on-failure` now runs after any `DeferOnFailure` callbacks, also pauses when a spec times out, and no longer pauses if the suite has already been interrupted. + ## 2.33.1 ### Fixes diff --git a/vendor/github.com/onsi/ginkgo/v2/core_dsl.go b/vendor/github.com/onsi/ginkgo/v2/core_dsl.go index fb5761c1..dc16a23c 100644 --- a/vendor/github.com/onsi/ginkgo/v2/core_dsl.go +++ b/vendor/github.com/onsi/ginkgo/v2/core_dsl.go @@ -883,6 +883,50 @@ func DeferCleanup(args ...any) { pushNode(internal.NewCleanupNode(deprecationTracker, fail, args...)) } +/* +DeferOnFailure registers a callback that Ginkgo runs if - and only if - the spec fails. The callback runs as soon as the +failure is recorded and before any of the spec's teardown (JustAfterEach, AfterEach, AfterAll, DeferCleanup) runs, so it +sees the system in the state it was in when the spec failed. This makes it the right place to capture failure artifacts: +screenshots, logs, dumps of cluster state, etc. + + BeforeEach(func(ctx SpecContext) { + browser := StartBrowser(ctx) + DeferOnFailure(func(ctx SpecContext) { + AddReportEntry("screenshot", browser.Screenshot(ctx), ReportEntryVisibilityFailureOrVerbose) + }, NodeTimeout(10*time.Second)) + DeferCleanup(browser.Close) + }) + +DeferOnFailure accepts exactly what DeferCleanup accepts: any function, along with arguments to pass to it. If the function's +last return value is a non-nil error the callback fails. If the function's first parameter is a SpecContext (or a +context.Context that you don't provide yourself) the callback is interruptible: Ginkgo cancels its context when the callback's +NodeTimeout elapses or when the suite is interrupted. By default a callback gets one GracePeriod to run - pass NodeTimeout to +change that. The spec's own SpecTimeout does not apply to the callback. DeferOnFailure also accepts the GracePeriod, +PollProgressAfter, PollProgressInterval, and Offset decorators. + +DeferOnFailure callbacks share the lifetime of DeferCleanup: + +When DeferOnFailure is called in BeforeEach, JustBeforeEach, or It the callback covers the current spec. +When DeferOnFailure is called in BeforeAll (or in a BeforeEach marked OncePerOrdered) the callback covers every spec in the ordered container. +When DeferOnFailure is called in BeforeSuite or in the all-processes function of SynchronizedBeforeSuite the callback covers every spec in the suite, as well as the suite-level setup and teardown nodes. + +The callbacks run at most once per spec attempt, on the first failure: a failed assertion, a panic, a timeout, an AbortSuite, +or the first interrupt. If the first failure occurs during teardown the callbacks run right after the failing node and +before the rest of the teardown. When several callbacks are registered they run in reverse order of registration, just +like DeferCleanup. A callback that fails is recorded as an additional failure on the spec, and the remaining callbacks +and teardown still run. + +When --sleep-on-failure is set, Ginkgo pauses after all DeferOnFailure callbacks have run. + +You can learn more about DeferOnFailure here: https://onsi.github.io/ginkgo/#capturing-failure-state-deferonfailure +*/ +func DeferOnFailure(args ...any) { + fail := func(message string, cl types.CodeLocation) { + global.Failer.Fail(message, cl) + } + pushNode(internal.NewOnFailureNode(deprecationTracker, fail, args...)) +} + /* AttachProgressReporter allows you to register a function that will be called whenever Ginkgo generates a Progress Report. The contents returned by the function will be included in the report. diff --git a/vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go b/vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go index db3e2484..0134f25b 100644 --- a/vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go +++ b/vendor/github.com/onsi/ginkgo/v2/ginkgo_t_dsl.go @@ -34,6 +34,7 @@ func GinkgoT(optionalOffset ...int) FullGinkgoTInterface { Fail, Skip, DeferCleanup, + DeferOnFailure, CurrentSpecReport, AddReportEntry, GinkgoRecover, @@ -100,6 +101,7 @@ type FullGinkgoTInterface interface { GinkgoRecover() DeferCleanup(args ...any) + DeferOnFailure(args ...any) RandomSeed() int64 ParallelProcess() int diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/group.go b/vendor/github.com/onsi/ginkgo/v2/internal/group.go index 781adf6f..eb48b644 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/group.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/group.go @@ -241,6 +241,17 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool { deadline = time.Now().Add(spec.SpecTimeout()) } + // DeferOnFailure callbacks run at most once per attempt, as soon as the first failure is recorded - + // before any (remaining) teardown runs + firedOnFailure := false + fireOnFailureIfNeeded := func() { + if firedOnFailure || !g.suite.shouldFireOnFailure(g.suite.currentSpecReport.State) { + return + } + firedOnFailure = true + g.suite.runOnFailureNodes(spec.Nodes.BestTextFor) + } + for _, node := range nodes { oncePair := pairs.runOncePairFor(node.ID) if !oncePair.isZero() && g.runOnceTracker[oncePair].Is(types.SpecStatePassed) { @@ -254,11 +265,70 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool { if g.suite.currentSpecReport.State != types.SpecStatePassed { terminatingNode, terminatingPair = node, oncePair failedInARunOnceBefore = !terminatingPair.isZero() + fireOnFailureIfNeeded() break } } afterNodeWasRun := map[uint]bool{} + onFailureScopeEnded := map[uint]bool{} + + // scopeEndsNow decides whether an after node (AfterEach, AfterAll, JustAfterEach), a DeferCleanup node, or + // a DeferOnFailure registration has reached the end of its scope on this attempt. After and cleanup nodes + // run when it does; DeferOnFailure registrations are dropped. + scopeEndsNow := func(node Node) bool { + var pair runOncePair + switch node.NodeType { + case types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll, types.NodeTypeOnFailureEach, types.NodeTypeOnFailureAll: + // check if we were generated in an AfterNode that has already run + if afterNodeWasRun[node.NodeIDWhereCleanupWasGenerated] { + return true // we were, so we should definitely run this cleanup now + } + // looks like this cleanup nodes was generated by a before node or it. + // the run-once status of a cleanup node is governed by the run-once status of its generator + pair = pairs.runOncePairFor(node.NodeIDWhereCleanupWasGenerated) + default: + pair = pairs.runOncePairFor(node.ID) + } + if pair.isZero() { + // this node is not governed by any run-once policy, we should run it + return true + } + // it's our last chance to run if we're the last spec for our oncePair + isLastSpecWithPair := g.isLastSpecWithPair(spec.SubjectID(), pair) + willRunAnotherAttempt := g.willRunAnotherAttempt(isFinalAttempt) + + switch g.suite.currentSpecReport.State { + case types.SpecStatePassed: //this attempt is passing... + return isLastSpecWithPair && !willRunAnotherAttempt //...we should run-once if this is our last chance + case types.SpecStateSkipped: //the spec was skipped by the user... + if isLastSpecWithPair { + return true //...we're the last spec, so we should run the AfterNode + } + if !terminatingPair.isZero() && terminatingNode.NestingLevel == node.NestingLevel { + return true //...or, a run-once node at our nesting level was skipped which means this is our last chance to run + } + case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateTimedout: // the spec has failed... + if !willRunAnotherAttempt { + if g.continueOnFailure { + return isLastSpecWithPair || failedInARunOnceBefore //...we're configured to continue on failures - so we should only run if we're the last spec for this pair or if we failed in a runOnceBefore (which means we _are_ the last spec to run) + } else { + return true //...this was the last attempt and continueOnFailure is false therefore we are the last spec to run and so the AfterNode should run + } + } + if !terminatingPair.isZero() { // ...and it failed in a run-once. which will be running again + if node.NodeType.Is(types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll | types.NodeTypeOnFailureEach | types.NodeTypeOnFailureAll) { + return terminatingNode.ID == node.NodeIDWhereCleanupWasGenerated // we should run this node if we're a clean-up generated by it + } else { + return terminatingNode.NestingLevel == node.NestingLevel // ...or if we're at the same nesting level + } + } + case types.SpecStateInterrupted, types.SpecStateAborted: // ...we've been interrupted and/or aborted + return true //...that means the test run is over and we should clean up the stack. Run the AfterNode + } + return false + } + includeDeferCleanups := false for { nodes := spec.Nodes.WithType(types.NodeTypeAfterEach) @@ -276,57 +346,19 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool { //this node has already been run on this attempt, don't rerun it return false } - var pair runOncePair - switch node.NodeType { - case types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll: - // check if we were generated in an AfterNode that has already run - if afterNodeWasRun[node.NodeIDWhereCleanupWasGenerated] { - return true // we were, so we should definitely run this cleanup now - } - // looks like this cleanup nodes was generated by a before node or it. - // the run-once status of a cleanup node is governed by the run-once status of its generator - pair = pairs.runOncePairFor(node.NodeIDWhereCleanupWasGenerated) - default: - pair = pairs.runOncePairFor(node.ID) - } - if pair.isZero() { - // this node is not governed by any run-once policy, we should run it - return true - } - // it's our last chance to run if we're the last spec for our oncePair - isLastSpecWithPair := g.isLastSpecWithPair(spec.SubjectID(), pair) - willRunAnotherAttempt := g.willRunAnotherAttempt(isFinalAttempt) - - switch g.suite.currentSpecReport.State { - case types.SpecStatePassed: //this attempt is passing... - return isLastSpecWithPair && !willRunAnotherAttempt //...we should run-once if this is our last chance - case types.SpecStateSkipped: //the spec was skipped by the user... - if isLastSpecWithPair { - return true //...we're the last spec, so we should run the AfterNode - } - if !terminatingPair.isZero() && terminatingNode.NestingLevel == node.NestingLevel { - return true //...or, a run-once node at our nesting level was skipped which means this is our last chance to run - } - case types.SpecStateFailed, types.SpecStatePanicked, types.SpecStateTimedout: // the spec has failed... - if !willRunAnotherAttempt { - if g.continueOnFailure { - return isLastSpecWithPair || failedInARunOnceBefore //...we're configured to continue on failures - so we should only run if we're the last spec for this pair or if we failed in a runOnceBefore (which means we _are_ the last spec to run) - } else { - return true //...this was the last attempt and continueOnFailure is false therefore we are the last spec to run and so the AfterNode should run - } - } - if !terminatingPair.isZero() { // ...and it failed in a run-once. which will be running again - if node.NodeType.Is(types.NodeTypeCleanupAfterEach | types.NodeTypeCleanupAfterAll) { - return terminatingNode.ID == node.NodeIDWhereCleanupWasGenerated // we should run this node if we're a clean-up generated by it - } else { - return terminatingNode.NestingLevel == node.NestingLevel // ...or if we're at the same nesting level - } + return scopeEndsNow(node) + }) + if includeDeferCleanups { + // note the DeferOnFailure registrations whose scope ends now - the moment the matching cleanups are selected. + // a scope that ends during this attempt stays ended, even if a failing cleanup later changes the spec's state + g.suite.selectiveLock.Lock() + for _, node := range g.suite.onFailureNodes { + if node.NodeType.Is(types.NodeTypeOnFailureEach|types.NodeTypeOnFailureAll) && scopeEndsNow(node) { + onFailureScopeEnded[node.ID] = true } - case types.SpecStateInterrupted, types.SpecStateAborted: // ...we've been interrupted and/or aborted - return true //...that means the test run is over and we should clean up the stack. Run the AfterNode } - return false - }) + g.suite.selectiveLock.Unlock() + } if len(nodes) == 0 && includeDeferCleanups { break @@ -342,10 +374,23 @@ func (g *group) attemptSpec(isFinalAttempt bool, spec Spec) bool { } else if state.Is(types.SpecStateFailureStates) { g.suite.currentSpecReport.AdditionalFailures = append(g.suite.currentSpecReport.AdditionalFailures, types.AdditionalFailure{State: state, Failure: failure}) } + // if this teardown node recorded the attempt's first failure, run the DeferOnFailure callbacks before the rest of teardown + fireOnFailureIfNeeded() } includeDeferCleanups = true } + // drop the DeferOnFailure registrations whose scope ended with this attempt. A registration shares the lifetime + // of a DeferCleanup registered by the same node: if such a cleanup would have run on this attempt, the registration is done. + g.suite.selectiveLock.Lock() + g.suite.onFailureNodes = g.suite.onFailureNodes.Filter(func(node Node) bool { + if !node.NodeType.Is(types.NodeTypeOnFailureEach | types.NodeTypeOnFailureAll) { + return true // suite-scoped registrations live for the whole suite + } + return !(onFailureScopeEnded[node.ID] || scopeEndsNow(node)) + }) + g.suite.selectiveLock.Unlock() + return failedInARunOnceBefore } @@ -431,4 +476,11 @@ func (g *group) run(specs Specs) { g.suite.currentSpecReport = types.SpecReport{} g.suite.selectiveLock.Unlock() } + + // no Each- or All-scoped DeferOnFailure registration outlives its group + g.suite.selectiveLock.Lock() + g.suite.onFailureNodes = g.suite.onFailureNodes.Filter(func(node Node) bool { + return !node.NodeType.Is(types.NodeTypeOnFailureEach | types.NodeTypeOnFailureAll) + }) + g.suite.selectiveLock.Unlock() } diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/node.go b/vendor/github.com/onsi/ginkgo/v2/internal/node.go index b0c8de8d..26a40da9 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/node.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/node.go @@ -647,8 +647,22 @@ func extractSynchronizedBeforeSuiteAllProcsBody(arg any) (func(SpecContext, []by var errInterface = reflect.TypeOf((*error)(nil)).Elem() func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(string, types.CodeLocation), args ...any) (Node, []error) { + return newDeferredNode(deprecationTracker, fail, types.NodeTypeCleanupInvalid, "DeferCleanup", types.GinkgoErrors.DeferCleanupInvalidFunction, args...) +} + +/* +NewOnFailureNode constructs the node registered by DeferOnFailure. DeferOnFailure accepts exactly what DeferCleanup accepts. +Like a cleanup node it is generated dynamically during the run phase - the suite assigns its final node type (and therefore +its scope) when it is pushed, based on the node that registered it. +*/ +func NewOnFailureNode(deprecationTracker *types.DeprecationTracker, fail func(string, types.CodeLocation), args ...any) (Node, []error) { + return newDeferredNode(deprecationTracker, fail, types.NodeTypeOnFailureInvalid, "DeferOnFailure", types.GinkgoErrors.DeferOnFailureInvalidFunction, args...) +} + +// newDeferredNode implements the shared argument handling of DeferCleanup and DeferOnFailure +func newDeferredNode(deprecationTracker *types.DeprecationTracker, fail func(string, types.CodeLocation), nodeType types.NodeType, name string, invalidFunctionError func(types.CodeLocation) error, args ...any) (Node, []error) { decorations, remainingArgs := PartitionDecorations(args...) - baseOffset := 2 + baseOffset := 3 // newDeferredNode -> NewCleanupNode/NewOnFailureNode -> DeferCleanup/DeferOnFailure -> user code cl := types.NewCodeLocation(baseOffset) finalArgs := []any{} for _, arg := range decorations { @@ -664,12 +678,12 @@ func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(stri finalArgs = append(finalArgs, cl) if len(remainingArgs) == 0 { - return Node{}, []error{types.GinkgoErrors.DeferCleanupInvalidFunction(cl)} + return Node{}, []error{invalidFunctionError(cl)} } callback := reflect.ValueOf(remainingArgs[0]) if !(callback.Kind() == reflect.Func) { - return Node{}, []error{types.GinkgoErrors.DeferCleanupInvalidFunction(cl)} + return Node{}, []error{invalidFunctionError(cl)} } callArgs := []reflect.Value{} @@ -693,7 +707,7 @@ func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(stri } last := out[len(out)-1] if last.Type().Implements(errInterface) && !last.IsNil() { - fail(fmt.Sprintf("DeferCleanup callback returned error: %v", last), cl) + fail(fmt.Sprintf("%s callback returned error: %v", name, last), cl) } } @@ -709,7 +723,7 @@ func NewCleanupNode(deprecationTracker *types.DeprecationTracker, fail func(stri }) } - return NewNode(deprecationTracker, types.NodeTypeCleanupInvalid, "", finalArgs) + return NewNode(deprecationTracker, nodeType, "", finalArgs) } func (n Node) IsZero() bool { diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/suite.go b/vendor/github.com/onsi/ginkgo/v2/internal/suite.go index 6c879855..02a58e55 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/suite.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/suite.go @@ -34,6 +34,17 @@ type Suite struct { cleanupNodes Nodes aroundNodes types.AroundNodes + // onFailureNodes holds the callbacks registered with DeferOnFailure, in registration order. + // Suite-scoped nodes live for the whole suite; Each- and All-scoped nodes are dropped by the group + // when the scope of the node that registered them ends (see group.attemptSpec). + onFailureNodes Nodes + // sleepOnFailureNodeID identifies the internal DeferOnFailure node that implements --sleep-on-failure + sleepOnFailureNodeID uint + // onFailureTriggerNodeType is the type of the node whose failure triggered the DeferOnFailure callbacks that are running + onFailureTriggerNodeType types.NodeType + // runningSynchronizedBeforeSuiteProc1Body is true while the proc-1-only function of SynchronizedBeforeSuite runs + runningSynchronizedBeforeSuiteProc1Body bool + failer *Failer reporter reporters.Reporter writer WriterInterface @@ -131,6 +142,10 @@ func (suite *Suite) Run(description string, suiteLabels Labels, suiteSemVerConst suite.deadline = time.Now().Add(suite.config.Timeout) } + if suite.config.SleepOnFailure > 0 { + suite.registerSleepOnFailureNode() + } + cancelProgressHandler := progressSignalRegistrar(suite.handleProgressSignal) success := suite.runSpecs(description, suiteLabels, suiteSemVerConstraints, suiteComponentSemVerConstraints, suitePath, hasProgrammaticFocus, specs) @@ -155,6 +170,10 @@ func (suite *Suite) PushNode(node Node) error { return suite.pushCleanupNode(node) } + if node.NodeType.Is(types.NodeTypesForOnFailure) { + return suite.pushOnFailureNode(node) + } + if node.NodeType.Is(types.NodeTypeBeforeSuite | types.NodeTypeAfterSuite | types.NodeTypeSynchronizedBeforeSuite | types.NodeTypeSynchronizedAfterSuite | types.NodeTypeBeforeSuite | types.NodeTypeReportBeforeSuite | types.NodeTypeReportAfterSuite) { return suite.pushSuiteNode(node) } @@ -270,6 +289,8 @@ func (suite *Suite) pushCleanupNode(node Node) error { return types.GinkgoErrors.PushingCleanupInReportingNode(node.CodeLocation, suite.currentNode.NodeType) case types.NodeTypeCleanupInvalid, types.NodeTypeCleanupAfterEach, types.NodeTypeCleanupAfterAll, types.NodeTypeCleanupAfterSuite: return types.GinkgoErrors.PushingCleanupInCleanupNode(node.CodeLocation) + case types.NodeTypeOnFailureInvalid, types.NodeTypeOnFailureEach, types.NodeTypeOnFailureAll, types.NodeTypeOnFailureSuite: + return types.GinkgoErrors.PushingCleanupInOnFailureNode(node.CodeLocation) default: node.NodeType = types.NodeTypeCleanupAfterEach } @@ -284,6 +305,151 @@ func (suite *Suite) pushCleanupNode(node Node) error { return nil } +func (suite *Suite) pushOnFailureNode(node Node) error { + // hold the lock for the whole registration: DeferOnFailure may be called from a goroutine spawned by the + // current node, and runNode updates currentNode under this lock + suite.selectiveLock.Lock() + defer suite.selectiveLock.Unlock() + + currentNode := suite.currentNode + if suite.phase != PhaseRun || currentNode.IsZero() { + return types.GinkgoErrors.PushingOnFailureNodeDuringTreeConstruction(node.CodeLocation) + } + + switch currentNode.NodeType { + case types.NodeTypeBeforeSuite: + node.NodeType = types.NodeTypeOnFailureSuite + case types.NodeTypeSynchronizedBeforeSuite: + if suite.runningSynchronizedBeforeSuiteProc1Body { + return types.GinkgoErrors.PushingOnFailureNodeInSynchronizedBeforeSuiteProc1(node.CodeLocation) + } + node.NodeType = types.NodeTypeOnFailureSuite + case types.NodeTypeBeforeAll: + node.NodeType = types.NodeTypeOnFailureAll + case types.NodeTypeBeforeEach, types.NodeTypeJustBeforeEach, types.NodeTypeIt: + // BeforeEach/JustBeforeEach nodes marked OncePerOrdered are governed by their run-once pair, just like DeferCleanup + node.NodeType = types.NodeTypeOnFailureEach + default: + return types.GinkgoErrors.PushingOnFailureNodeInInvalidNode(node.CodeLocation, currentNode.NodeType) + } + + node.NodeIDWhereCleanupWasGenerated = currentNode.ID + node.NestingLevel = currentNode.NestingLevel + node.AroundNodes = types.AroundNodes{}.Append(currentNode.AroundNodes...).Append(node.AroundNodes...) + if node.NodeTimeout == 0 { + // on-failure callbacks are diagnostic and must not hang the suite: bound them by one GracePeriod unless told otherwise + node.NodeTimeout = suite.config.GracePeriod + } + suite.onFailureNodes = append(suite.onFailureNodes, node) + + return nil +} + +/* +registerSleepOnFailureNode implements --sleep-on-failure as an internal, suite-scoped DeferOnFailure callback. + +It is registered before anything else, so it is the oldest registration and - because on-failure callbacks run +newest-first - it always runs last: every user and library callback captures the failed state first, and +then Ginkgo pauses with the system still live for inspection. +*/ +func (suite *Suite) registerSleepOnFailureNode() { + node := Node{ + ID: UniqueNodeID(), + NodeType: types.NodeTypeOnFailureSuite, + Text: "--sleep-on-failure", + CodeLocation: types.NewCustomCodeLocation("--sleep-on-failure"), + NestingLevel: -1, + HasContext: true, + PollProgressAfter: 0, // don't poll for progress while paused + PollProgressInterval: -1, + GracePeriod: -1, + Body: suite.sleepOnFailure, + } + suite.sleepOnFailureNodeID = node.ID + suite.onFailureNodes = append(Nodes{node}, suite.onFailureNodes...) +} + +func (suite *Suite) sleepOnFailure(ctx SpecContext) { + // only pause for failures in setup and subject nodes (It, Before*, BeforeSuite...). Pausing on a failure + // in a teardown, cleanup, or reporting node would be pointless: the system is already being torn down. + // (we look at the failure that triggered the callbacks: an earlier callback may since have replaced it, e.g. by calling AbortSuite) + suite.selectiveLock.Lock() + failureNodeType := suite.onFailureTriggerNodeType + suite.selectiveLock.Unlock() + if failureNodeType.Is(types.NodeTypesAllowedDuringCleanupInterrupt | types.NodeTypesAllowedDuringReportInterrupt) { + return + } + // once the user has interrupted the suite (whether to trigger these callbacks or while they ran) they want Ginkgo to + // move on: pausing now would ask them to interrupt again, and a second interrupt skips cleanup + if suite.interruptHandler.Status().Interrupted() { + return + } + + duration := suite.config.SleepOnFailure + report := suite.generateProgressReport(false) + report.Message = fmt.Sprintf("{{bold}}{{orange}}Paused on failure for up to %s.{{/}}\nThe spec failed and Ginkgo has paused before running any teardown so you can inspect the live system.\nPress {{bold}}^C{{/}} to end the pause and proceed to cleanup.", duration) + suite.emitProgressReport(report) + + timer := time.NewTimer(duration) + defer timer.Stop() + // wait for the pause to elapse, or for the context to be cancelled (e.g. by the user interrupting + // the suite) - in which case we end the pause early so cleanup can proceed + select { + case <-timer.C: + case <-ctx.Done(): + } +} + +/* +shouldFireOnFailure decides whether the outcome of a node should trigger the DeferOnFailure callbacks. + +Failures, panics, timeouts (spec, node, and suite) and aborts all fire. Interrupts only fire when they come from the +user's first signal: that is the level at which Ginkgo still runs cleanup. Subsequent signals (which skip cleanup) and +interrupts caused by another process aborting (this spec did not fail - it's collateral) do not fire. +*/ +func (suite *Suite) shouldFireOnFailure(state types.SpecState) bool { + if state.Is(types.SpecStateFailed | types.SpecStatePanicked | types.SpecStateTimedout | types.SpecStateAborted) { + return true + } + if state.Is(types.SpecStateInterrupted) { + status := suite.interruptHandler.Status() + return status.Cause == interrupt_handler.InterruptCauseSignal && status.Level == interrupt_handler.InterruptLevelCleanupAndReport + } + return false +} + +/* +runOnFailureNodes runs every registered DeferOnFailure callback, newest first, and folds their outcomes into the +current spec report. A failing callback is recorded as an additional failure - it never replaces the failure that +triggered it - though an AbortSuite in a callback is honored. + +This is called by the group (and by runSuiteNode) between nodes, so no Ginkgo lock is held while the callbacks run. +*/ +func (suite *Suite) runOnFailureNodes(textFor func(Node) string) { + suite.selectiveLock.Lock() + nodes := suite.onFailureNodes.Reverse() + suite.onFailureTriggerNodeType = suite.currentSpecReport.Failure.FailureNodeType + suite.selectiveLock.Unlock() + + for _, node := range nodes { + state, failure := suite.runNode(node, time.Time{}, textFor(node)) + if node.ID == suite.sleepOnFailureNodeID { + // the pause's own outcome (e.g. the user interrupting it) never affects the spec + continue + } + // a callback can call AbortSuite directly, or after it has timed out (in which case the abort is recorded as the timeout's additional failure) + aborted := state == types.SpecStateAborted || (failure.AdditionalFailure != nil && failure.AdditionalFailure.State == types.SpecStateAborted) + if aborted && suite.currentSpecReport.State != types.SpecStateAborted { + // honor the abort, but keep the failure that triggered the callbacks rather than replacing it + suite.currentSpecReport.AdditionalFailures = append(suite.currentSpecReport.AdditionalFailures, types.AdditionalFailure{State: suite.currentSpecReport.State, Failure: suite.currentSpecReport.Failure}) + suite.currentSpecReport.State = types.SpecStateAborted + suite.currentSpecReport.Failure = failure + } else if state.Is(types.SpecStateFailureStates) { + suite.currentSpecReport.AdditionalFailures = append(suite.currentSpecReport.AdditionalFailures, types.AdditionalFailure{State: state, Failure: failure}) + } + } +} + func (suite *Suite) generateTimelineLocation() types.TimelineLocation { suite.selectiveLock.Lock() defer suite.selectiveLock.Unlock() @@ -395,18 +561,28 @@ func (suite *Suite) AddReportEntry(entry ReportEntry) error { func (suite *Suite) generateProgressReport(fullReport bool) types.ProgressReport { timelineLocation := suite.generateTimelineLocation() + + // snapshot the state we need under the lock... suite.selectiveLock.Lock() - defer suite.selectiveLock.Unlock() + currentSpecContext := suite.currentSpecContext + currentSpecReport := suite.currentSpecReport + currentNode := suite.currentNode + currentNodeStartTime := suite.currentNodeStartTime + currentByStep := suite.currentByStep + gwOutput := suite.currentSpecReport.CapturedGinkgoWriterOutput + string(suite.writer.Bytes()) + suite.selectiveLock.Unlock() + // ...and query the progress reporters *without* holding it. Reporters are user code and + // are free to call back into Ginkgo (e.g. CurrentSpecReport() or AddReportEntry()), both of + // which take selectiveLock. Holding the lock here would deadlock them until the deadline. deadline, cancel := context.WithTimeout(context.Background(), ProgressReporterDeadline) defer cancel() var additionalReports []string - if suite.currentSpecContext != nil { - additionalReports = append(additionalReports, suite.currentSpecContext.QueryProgressReporters(deadline, suite.failer)...) + if currentSpecContext != nil { + additionalReports = append(additionalReports, currentSpecContext.QueryProgressReporters(deadline, suite.failer)...) } additionalReports = append(additionalReports, suite.QueryProgressReporters(deadline, suite.failer)...) - gwOutput := suite.currentSpecReport.CapturedGinkgoWriterOutput + string(suite.writer.Bytes()) - pr, err := NewProgressReport(suite.isRunningInParallel(), suite.currentSpecReport, suite.currentNode, suite.currentNodeStartTime, suite.currentByStep, gwOutput, timelineLocation, additionalReports, suite.config.SourceRoots, fullReport) + pr, err := NewProgressReport(suite.isRunningInParallel(), currentSpecReport, currentNode, currentNodeStartTime, currentByStep, gwOutput, timelineLocation, additionalReports, suite.config.SourceRoots, fullReport) if err != nil { fmt.Printf("{{red}}Failed to generate progress report:{{/}}\n%s\n", err.Error()) @@ -655,6 +831,16 @@ func (suite *Suite) runSuiteNode(node Node) { suite.outputInterceptor.StartInterceptingOutput() suite.currentSpecReport.StartTime = time.Now() + // if this suite-level node fails, run any (suite-scoped) DeferOnFailure callbacks - once, and before any further teardown + firedOnFailure := false + fireOnFailureIfNeeded := func() { + if firedOnFailure || !suite.shouldFireOnFailure(suite.currentSpecReport.State) { + return + } + firedOnFailure = true + suite.runOnFailureNodes(func(Node) string { return "" }) + } + var err error switch node.NodeType { case types.NodeTypeBeforeSuite, types.NodeTypeAfterSuite: @@ -676,7 +862,13 @@ func (suite *Suite) runSuiteNode(node Node) { } node.Body = func(c SpecContext) { data = node.SynchronizedBeforeSuiteProc1Body(c) } node.HasContext = node.SynchronizedBeforeSuiteProc1BodyHasContext + suite.selectiveLock.Lock() + suite.runningSynchronizedBeforeSuiteProc1Body = true + suite.selectiveLock.Unlock() suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + suite.selectiveLock.Lock() + suite.runningSynchronizedBeforeSuiteProc1Body = false + suite.selectiveLock.Unlock() if suite.config.ParallelTotal > 1 { suite.currentSpecReport.CapturedStdOutErr += suite.outputInterceptor.StopInterceptingAndReturnOutput() suite.outputInterceptor.StartInterceptingOutput() @@ -708,6 +900,8 @@ func (suite *Suite) runSuiteNode(node Node) { node.Body = node.SynchronizedAfterSuiteAllProcsBody node.HasContext = node.SynchronizedAfterSuiteAllProcsBodyHasContext suite.currentSpecReport.State, suite.currentSpecReport.Failure = suite.runNode(node, time.Time{}, "") + // run the callbacks before process #1 waits for the other processes and runs its own teardown + fireOnFailureIfNeeded() if suite.config.ParallelProcess == 1 { if suite.config.ParallelTotal > 1 { err = suite.client.BlockUntilNonprimaryProcsHaveFinished() @@ -733,6 +927,8 @@ func (suite *Suite) runSuiteNode(node Node) { suite.reporter.EmitFailure(suite.currentSpecReport.State, suite.currentSpecReport.Failure) } + fireOnFailureIfNeeded() + suite.currentSpecReport.EndTime = time.Now() suite.currentSpecReport.RunTime = suite.currentSpecReport.EndTime.Sub(suite.currentSpecReport.StartTime) suite.currentSpecReport.CapturedGinkgoWriterOutput = string(suite.writer.Bytes()) @@ -885,6 +1081,11 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ } } + if suite.sleepOnFailureNodeID != 0 && node.ID == suite.sleepOnFailureNodeID { + // --sleep-on-failure pauses for the requested duration (or until the user interrupts) regardless of any suite deadline + deadline = time.Time{} + } + if !node.HasContext { // this maps onto the pre-context behavior: // - an interrupted node exits immediately. with this, context-less nodes that are in a spec with a SpecTimeout and/or are interrupted by other means will simply exit immediately after the timeout/interrupt @@ -996,7 +1197,6 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ } else { failure.Message, failure.Location, failure.ForwardedPanic, failure.TimelineLocation = failureFromRun.Message, failureFromRun.Location, failureFromRun.ForwardedPanic, failureFromRun.TimelineLocation suite.reporter.EmitFailure(outcomeFromRun, failure) - suite.pauseOnFailureIfRequested(node) return outcomeFromRun, failure } case <-gracePeriodChannel: @@ -1080,42 +1280,6 @@ func (suite *Suite) runNode(node Node, specDeadline time.Time, text string) (typ } } -// pauseOnFailureIfRequested pauses the suite at the moment a failure is identified, -// when the user has set --sleep-on-failure. This hooks directly into runNode's failure -// path so the pause happens immediately at the point of failure - before any teardown -// or cleanup runs - leaving the system live for inspection. -// -// We only pause for failures in setup and subject nodes (It, Before*, BeforeSuite...), -// i.e. nodes that run before teardown. Pausing on a failure in a teardown/cleanup or -// reporting node would be pointless (the system is already being torn down) and could -// interfere with interrupt handling, so those are skipped. -// -// The pause is interruptible: pressing ^C (or any interrupt) ends the pause early and -// the suite proceeds to run cleanup as usual. It is a no-op if the feature is disabled. -func (suite *Suite) pauseOnFailureIfRequested(node Node) { - if suite.config.SleepOnFailure <= 0 { - return - } - // only pause before teardown - skip teardown/cleanup/reporting nodes - if node.NodeType.Is(types.NodeTypesAllowedDuringCleanupInterrupt | types.NodeTypesAllowedDuringReportInterrupt) { - return - } - - duration := suite.config.SleepOnFailure - report := suite.generateProgressReport(false) - report.Message = fmt.Sprintf("{{bold}}{{orange}}Paused on failure for up to %s.{{/}}\nThe spec failed and Ginkgo has paused before running any teardown so you can inspect the live system.\nPress {{bold}}^C{{/}} to end the pause and proceed to cleanup.", duration) - suite.emitProgressReport(report) - - timer := time.NewTimer(duration) - defer timer.Stop() - // wait for the pause to elapse, or for the user to interrupt - in which case we end - // the pause early and let runNode return so cleanup can proceed - select { - case <-timer.C: - case <-suite.interruptHandler.Status().Channel: - } -} - // TODO: search for usages and consider if reporter.EmitFailure() is necessary func (suite *Suite) failureForLeafNodeWithMessage(node Node, message string) types.Failure { return types.Failure{ diff --git a/vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go b/vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go index e6fbaee4..e7a4d971 100644 --- a/vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go +++ b/vendor/github.com/onsi/ginkgo/v2/internal/testingtproxy/testing_t_proxy.go @@ -15,6 +15,7 @@ import ( type failFunc func(message string, callerSkip ...int) type skipFunc func(message string, callerSkip ...int) type cleanupFunc func(args ...any) +type onFailureFunc func(args ...any) type reportFunc func() types.SpecReport type addReportEntryFunc func(names string, args ...any) type ginkgoWriterInterface interface { @@ -32,13 +33,14 @@ var formatters = map[bool]formatter.Formatter{ false: formatter.NewWithNoColorBool(false), } -func New(writer ginkgoWriterInterface, fail failFunc, skip skipFunc, cleanup cleanupFunc, report reportFunc, addReportEntry addReportEntryFunc, ginkgoRecover ginkgoRecoverFunc, attachProgressReporter attachProgressReporterFunc, randomSeed int64, parallelProcess int, parallelTotal int, noColor bool, offset int) *ginkgoTestingTProxy { +func New(writer ginkgoWriterInterface, fail failFunc, skip skipFunc, cleanup cleanupFunc, onFailure onFailureFunc, report reportFunc, addReportEntry addReportEntryFunc, ginkgoRecover ginkgoRecoverFunc, attachProgressReporter attachProgressReporterFunc, randomSeed int64, parallelProcess int, parallelTotal int, noColor bool, offset int) *ginkgoTestingTProxy { return &ginkgoTestingTProxy{ fail: fail, offset: offset, writer: writer, skip: skip, cleanup: cleanup, + onFailure: onFailure, report: report, addReportEntry: addReportEntry, ginkgoRecover: ginkgoRecover, @@ -54,6 +56,7 @@ type ginkgoTestingTProxy struct { fail failFunc skip skipFunc cleanup cleanupFunc + onFailure onFailureFunc report reportFunc offset int writer ginkgoWriterInterface @@ -231,6 +234,10 @@ func (t *ginkgoTestingTProxy) DeferCleanup(args ...any) { finalArgs := []any{internal.Offset(1)} t.cleanup(append(finalArgs, args...)...) } +func (t *ginkgoTestingTProxy) DeferOnFailure(args ...any) { + finalArgs := []any{internal.Offset(1)} + t.onFailure(append(finalArgs, args...)...) +} func (t *ginkgoTestingTProxy) RandomSeed() int64 { return t.randomSeed } diff --git a/vendor/github.com/onsi/ginkgo/v2/types/errors.go b/vendor/github.com/onsi/ginkgo/v2/types/errors.go index 636070c1..54652792 100644 --- a/vendor/github.com/onsi/ginkgo/v2/types/errors.go +++ b/vendor/github.com/onsi/ginkgo/v2/types/errors.go @@ -344,6 +344,52 @@ func (g ginkgoErrors) PushingCleanupInCleanupNode(cl CodeLocation) error { } } +func (g ginkgoErrors) PushingCleanupInOnFailureNode(cl CodeLocation) error { + return GinkgoError{ + Heading: "DeferCleanup cannot be called in a DeferOnFailure callback", + Message: "DeferOnFailure callbacks run while the spec is failing, just before its teardown. Register cleanup in the setup or subject node that created the resource instead.", + CodeLocation: cl, + DocLink: "capturing-failure-state-deferonfailure", + } +} + +/* DeferOnFailure errors */ +func (g ginkgoErrors) DeferOnFailureInvalidFunction(cl CodeLocation) error { + return GinkgoError{ + Heading: "DeferOnFailure requires a valid function", + Message: "You must pass DeferOnFailure a function to invoke. If the function's last return value is a non-nil error, the callback fails; any other return values are ignored. The function can take arbitrarily many arguments and you should provide these to DeferOnFailure to pass along to the function.", + CodeLocation: cl, + DocLink: "capturing-failure-state-deferonfailure", + } +} + +func (g ginkgoErrors) PushingOnFailureNodeDuringTreeConstruction(cl CodeLocation) error { + return GinkgoError{ + Heading: "DeferOnFailure must be called inside a setup or subject node", + Message: "You must call DeferOnFailure inside BeforeSuite, SynchronizedBeforeSuite (in the function that runs on all processes), BeforeAll, BeforeEach, JustBeforeEach, or It. You can't call DeferOnFailure at the top-level or in a container node.", + CodeLocation: cl, + DocLink: "capturing-failure-state-deferonfailure", + } +} + +func (g ginkgoErrors) PushingOnFailureNodeInInvalidNode(cl CodeLocation, nodeType NodeType) error { + return GinkgoError{ + Heading: fmt.Sprintf("DeferOnFailure cannot be called in %s", nodeType), + Message: "DeferOnFailure registers a callback that runs before teardown when a failure occurs. It can only be called from BeforeSuite, SynchronizedBeforeSuite (in the function that runs on all processes), BeforeAll, BeforeEach, JustBeforeEach, or It - not from teardown, reporting, cleanup, or other DeferOnFailure callbacks.", + CodeLocation: cl, + DocLink: "capturing-failure-state-deferonfailure", + } +} + +func (g ginkgoErrors) PushingOnFailureNodeInSynchronizedBeforeSuiteProc1(cl CodeLocation) error { + return GinkgoError{ + Heading: "DeferOnFailure cannot be called in the first function passed to SynchronizedBeforeSuite", + Message: "That function only runs on process #1, so a DeferOnFailure registered there would only cover the specs that run on process #1. Call DeferOnFailure in the second function passed to SynchronizedBeforeSuite (which runs on all processes) instead.", + CodeLocation: cl, + DocLink: "capturing-failure-state-deferonfailure", + } +} + /* ReportEntry errors */ func (g ginkgoErrors) TooManyReportEntryValues(cl CodeLocation, arg any) error { return GinkgoError{ diff --git a/vendor/github.com/onsi/ginkgo/v2/types/types.go b/vendor/github.com/onsi/ginkgo/v2/types/types.go index 16277443..a448fbbc 100644 --- a/vendor/github.com/onsi/ginkgo/v2/types/types.go +++ b/vendor/github.com/onsi/ginkgo/v2/types/types.go @@ -946,11 +946,17 @@ const ( NodeTypeCleanupAfterEach NodeTypeCleanupAfterAll NodeTypeCleanupAfterSuite + + NodeTypeOnFailureInvalid + NodeTypeOnFailureEach + NodeTypeOnFailureAll + NodeTypeOnFailureSuite ) var NodeTypesForContainerAndIt = NodeTypeContainer | NodeTypeIt var NodeTypesForSuiteLevelNodes = NodeTypeBeforeSuite | NodeTypeSynchronizedBeforeSuite | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite | NodeTypeCleanupAfterSuite -var NodeTypesAllowedDuringCleanupInterrupt = NodeTypeAfterEach | NodeTypeJustAfterEach | NodeTypeAfterAll | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeCleanupAfterEach | NodeTypeCleanupAfterAll | NodeTypeCleanupAfterSuite +var NodeTypesForOnFailure = NodeTypeOnFailureInvalid | NodeTypeOnFailureEach | NodeTypeOnFailureAll | NodeTypeOnFailureSuite +var NodeTypesAllowedDuringCleanupInterrupt = NodeTypeAfterEach | NodeTypeJustAfterEach | NodeTypeAfterAll | NodeTypeAfterSuite | NodeTypeSynchronizedAfterSuite | NodeTypeCleanupAfterEach | NodeTypeCleanupAfterAll | NodeTypeCleanupAfterSuite | NodeTypeOnFailureEach | NodeTypeOnFailureAll | NodeTypeOnFailureSuite var NodeTypesAllowedDuringReportInterrupt = NodeTypeReportBeforeEach | NodeTypeReportAfterEach | NodeTypeReportBeforeSuite | NodeTypeReportAfterSuite var ntEnumSupport = NewEnumSupport(map[uint]string{ @@ -975,6 +981,10 @@ var ntEnumSupport = NewEnumSupport(map[uint]string{ uint(NodeTypeCleanupAfterEach): "DeferCleanup (Each)", uint(NodeTypeCleanupAfterAll): "DeferCleanup (All)", uint(NodeTypeCleanupAfterSuite): "DeferCleanup (Suite)", + uint(NodeTypeOnFailureInvalid): "DeferOnFailure", + uint(NodeTypeOnFailureEach): "DeferOnFailure (Each)", + uint(NodeTypeOnFailureAll): "DeferOnFailure (All)", + uint(NodeTypeOnFailureSuite): "DeferOnFailure (Suite)", }) func (nt NodeType) String() string { diff --git a/vendor/github.com/onsi/ginkgo/v2/types/version.go b/vendor/github.com/onsi/ginkgo/v2/types/version.go index 7d8aa801..c4815ceb 100644 --- a/vendor/github.com/onsi/ginkgo/v2/types/version.go +++ b/vendor/github.com/onsi/ginkgo/v2/types/version.go @@ -1,3 +1,3 @@ package types -const VERSION = "2.33.1" +const VERSION = "2.34.0" diff --git a/vendor/go.yaml.in/yaml/v3/resolve.go b/vendor/go.yaml.in/yaml/v3/resolve.go index 64ae8880..d47f50e6 100644 --- a/vendor/go.yaml.in/yaml/v3/resolve.go +++ b/vendor/go.yaml.in/yaml/v3/resolve.go @@ -42,6 +42,7 @@ func init() { for _, c := range "yYnNtTfFoO~" { t[int(c)] = 'M' // In map } + t[int('<')] = 'M' // In map (merge key "<<") t[int('.')] = '.' // Float (potentially in map) var resolveMapList = []struct { diff --git a/vendor/golang.org/x/net/html/parse.go b/vendor/golang.org/x/net/html/parse.go index 165b6108..a99cee66 100644 --- a/vendor/golang.org/x/net/html/parse.go +++ b/vendor/golang.org/x/net/html/parse.go @@ -51,6 +51,9 @@ type parser struct { // context is the context element when parsing an HTML fragment // (section 12.4). context *Node + // textNodes holds buffers for TextNodes that have had multiple text + // tokens coalesced into them. + textNodes map[*Node][]byte } func (p *parser) top() *Node { @@ -292,7 +295,7 @@ func (p *parser) fosterParent(n *Node) { prev = parent.LastChild } if prev != nil && prev.Type == TextNode && n.Type == TextNode { - prev.Data += n.Data + p.appendText(prev, n.Data) return } @@ -316,7 +319,7 @@ func (p *parser) addText(text string) { t := p.top() if n := t.LastChild; n != nil && n.Type == TextNode { - n.Data += text + p.appendText(n, text) return } p.addChild(&Node{ @@ -325,6 +328,18 @@ func (p *parser) addText(text string) { }) } +func (p *parser) appendText(n *Node, text string) { + if p.textNodes == nil { + p.textNodes = make(map[*Node][]byte) + } + b, ok := p.textNodes[n] + if !ok { + b = []byte(n.Data) + n.Data = "" + } + p.textNodes[n] = append(b, text...) +} + func attrCompare(a, b Attribute) int { return cmp.Or( cmp.Compare(a.Namespace, b.Namespace), @@ -2242,6 +2257,10 @@ func (p *parser) parse() (err error) { } p.parseCurrentToken() } + for n, b := range p.textNodes { + n.Data = string(b) + } + p.textNodes = nil return nil } diff --git a/vendor/golang.org/x/net/html/render.go b/vendor/golang.org/x/net/html/render.go index 767eeae2..f356d48b 100644 --- a/vendor/golang.org/x/net/html/render.go +++ b/vendor/golang.org/x/net/html/render.go @@ -246,18 +246,17 @@ func childTextNodesAreLiteral(n *Node) bool { switch n.Data { case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": - // We need to check if n is a node that was fostered from a HTML namespace - // into a non-HTML namespace (in which case, different rules apply to it). - // We do this by walking up the tree until we find a node with a non-empty - // namespace. If we find such a node, we also have to check if it's - // an HTML integration point. If it isn't, then the node we're currently - // looking at is foster-parented and we should return false. - for p := n.Parent; p != nil; p = p.Parent { - if p.Namespace != "" { - if !htmlIntegrationPoint(p) { - return false - } - break + // n's text is literal only if the parser would keep n in the HTML + // namespace when the output is re-parsed. An ancestor may have been + // fostered under a foreign element (such as an HTML under + // ) where the parser would instead create it, and its + // descendants, in that element's namespace. We walk up the whole tree + // and check that every transition out of a non-HTML namespace happens + // at an HTML integration point. This is stricter than the parser, so + // some text that would be safe literally is escaped. + for c, p := n, n.Parent; p != nil; c, p = p, p.Parent { + if p.Namespace != "" && p.Namespace != c.Namespace && !htmlIntegrationPoint(p) { + return false } } diff --git a/vendor/modules.txt b/vendor/modules.txt index b7977c7e..2bcabe4e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -9,7 +9,7 @@ code.cloudfoundry.org/cfhttp/v2 # code.cloudfoundry.org/debugserver v0.121.0 ## explicit; go 1.26.0 code.cloudfoundry.org/debugserver -# code.cloudfoundry.org/lager/v3 v3.93.0 +# code.cloudfoundry.org/lager/v3 v3.94.0 ## explicit; go 1.26.0 code.cloudfoundry.org/lager/v3 code.cloudfoundry.org/lager/v3/internal/truncate @@ -66,7 +66,7 @@ github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value -# github.com/google/pprof v0.0.0-20261026192309-dbd0ed30aa88 +# github.com/google/pprof v0.0.0-20261102214848-0031fcd306b0 ## explicit; go 1.26.0 github.com/google/pprof/profile # github.com/jackc/pgpassfile v1.0.0 @@ -80,7 +80,7 @@ github.com/google/pprof/profile github.com/nu7hatch/gouuid # github.com/nxadm/tail v1.4.11 ## explicit; go 1.13 -# github.com/onsi/ginkgo/v2 v2.33.1 +# github.com/onsi/ginkgo/v2 v2.34.0 ## explicit; go 1.25.0 github.com/onsi/ginkgo/v2 github.com/onsi/ginkgo/v2/config @@ -133,13 +133,13 @@ github.com/tedsuo/ifrit/sigmon # github.com/tedsuo/rata v1.0.0 ## explicit github.com/tedsuo/rata -# go.yaml.in/yaml/v3 v3.0.5 +# go.yaml.in/yaml/v3 v3.0.6 ## explicit; go 1.16 go.yaml.in/yaml/v3 # golang.org/x/mod v0.42.0 ## explicit; go 1.26.0 golang.org/x/mod/semver -# golang.org/x/net v0.61.0 +# golang.org/x/net v0.62.0 ## explicit; go 1.26.0 golang.org/x/net/html golang.org/x/net/html/atom